From 2c4d0320bff18167d8018458bd30067e124d1d07 Mon Sep 17 00:00:00 2001 From: core Date: Sat, 22 Nov 2025 21:34:22 -0500 Subject: [PATCH] feat: the most complicated thrust system OF ALL TIME! (certified) --- .cargo/config.toml | 2 +- Cargo.lock | 182 +++++++++++++++- crates/launcher/Cargo.toml | 2 +- crates/unified/Cargo.toml | 11 +- .../assets/config/parts/hearty.part.toml | 19 +- .../assets/config/parts/housing.part.toml | 4 + crates/unified/src/attachment.rs | 2 +- crates/unified/src/client/input/mod.rs | 36 +++- crates/unified/src/client/input/util.rs | 11 + crates/unified/src/client/key_input.rs | 32 +-- crates/unified/src/client/mod.rs | 5 +- crates/unified/src/client/ship/mod.rs | 3 +- crates/unified/src/client/ship/thrusters.rs | 204 ++++++++++++++++++ crates/unified/src/client_plugins.rs | 2 + crates/unified/src/main.rs | 1 + crates/unified/src/server/mod.rs | 1 - crates/unified/src/server/thruster/mod.rs | 3 - crates/unified/src/thrust.rs | 11 + rust-toolchain.toml | 2 - 19 files changed, 488 insertions(+), 45 deletions(-) create mode 100644 crates/unified/src/client/input/util.rs create mode 100644 crates/unified/src/client/ship/thrusters.rs delete mode 100644 crates/unified/src/server/thruster/mod.rs create mode 100644 crates/unified/src/thrust.rs delete mode 100644 rust-toolchain.toml diff --git a/.cargo/config.toml b/.cargo/config.toml index dc06211351d14c6d0c56ae46e567bed8146553ab..811f04f537b7f0c25eb43b9725060d61a1ed4633 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -6,7 +6,7 @@ xtask = "run --release --package xtask --" [target.x86_64-unknown-linux-gnu] linker = "clang" -rustflags = ["-C", "link-arg=-fuse-ld=mold", "-Zshare-generics=y"] +rustflags = ["-C", "link-arg=-fuse-ld=mold"] [target.wasm32-unknown-unknown] rustflags = ['--cfg', 'getrandom_backend="wasm_js"'] diff --git a/Cargo.lock b/Cargo.lock index 43849a4dbeb3e873917c3f1243daf170c0721bcc..ce70a838569ac2119b5a0ac2b20544ac73ba2d71 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1188,6 +1188,22 @@ dependencies = [ "encase_derive_impl", ] +[[package]] +name = "bevy_gilrs" +version = "0.17.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "28ff35087f25406006338e6d57f31f313a60f3a5e09990ab7c7b5203b0b55077" +dependencies = [ + "bevy_app", + "bevy_ecs", + "bevy_input", + "bevy_platform", + "bevy_time", + "gilrs", + "thiserror 2.0.17", + "tracing", +] + [[package]] name = "bevy_gizmos" version = "0.17.2" @@ -1321,6 +1337,7 @@ dependencies = [ "bevy_dev_tools", "bevy_diagnostic", "bevy_ecs", + "bevy_gilrs", "bevy_gizmos", "bevy_image", "bevy_input", @@ -3162,6 +3179,24 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "92773504d58c093f6de2459af4af33faa518c13451eb8f2b5698ed3d36e7c813" +[[package]] +name = "dyn-clone" +version = "1.0.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555" + +[[package]] +name = "dyn-eq" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c2d035d21af5cde1a6f5c7b444a5bf963520a9f142e5d06931178433d7d5388" + +[[package]] +name = "dyn-hash" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15401da73a9ed8c80e3b2d4dc05fe10e7b72d7243b9f614e516a44fa99986e88" + [[package]] name = "ecolor" version = "0.32.3" @@ -3977,6 +4012,40 @@ dependencies = [ "weezl", ] +[[package]] +name = "gilrs" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbb2c998745a3c1ac90f64f4f7b3a54219fd3612d7705e7798212935641ed18f" +dependencies = [ + "fnv", + "gilrs-core", + "log", + "uuid", + "vec_map", +] + +[[package]] +name = "gilrs-core" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "be11a71ac3564f6965839e2ed275bf4fcf5ce16d80d396e1dfdb7b2d80bd587e" +dependencies = [ + "core-foundation 0.10.1", + "inotify 0.11.0", + "io-kit-sys", + "js-sys", + "libc", + "libudev-sys", + "log", + "nix", + "uuid", + "vec_map", + "wasm-bindgen", + "web-sys", + "windows 0.61.3", +] + [[package]] name = "gimli" version = "0.32.3" @@ -4648,6 +4717,16 @@ dependencies = [ "rustversion", ] +[[package]] +name = "io-kit-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "617ee6cf8e3f66f3b4ea67a4058564628cde41901316e19f559e14c7c72c5e7b" +dependencies = [ + "core-foundation-sys", + "mach2", +] + [[package]] name = "is-terminal" version = "0.4.17" @@ -4821,7 +4900,6 @@ dependencies = [ "eframe", "egui 0.32.3", "egui_extras", - "starkingdoms", ] [[package]] @@ -4830,6 +4908,34 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" +[[package]] +name = "leafwing-input-manager" +version = "0.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7547ce653e70ffcd6cf1d040ada864e04d293167f237b32c6520ecbf18150b0a" +dependencies = [ + "bevy", + "dyn-clone", + "dyn-eq", + "dyn-hash", + "itertools 0.14.0", + "leafwing_input_manager_macros", + "serde", + "serde_flexitos", +] + +[[package]] +name = "leafwing_input_manager_macros" +version = "0.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2226cb83129176a6c634f2ce0828c2c29896ea0898fc198636f98696b8056890" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "libc" version = "0.2.177" @@ -4875,6 +4981,16 @@ dependencies = [ "redox_syscall 0.5.18", ] +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + [[package]] name = "libz-sys" version = "1.1.23" @@ -4953,6 +5069,15 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "mach2" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d640282b302c0bb0a2a8e0233ead9035e3bed871f0b7e81fe4a1ec829765db44" +dependencies = [ + "libc", +] + [[package]] name = "malloc_buf" version = "0.0.6" @@ -5044,6 +5169,16 @@ dependencies = [ "paste", ] +[[package]] +name = "microlp" +version = "0.2.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d1790c73b93164ff65868f63164497cb32339458a9297e17e212d91df62258" +dependencies = [ + "log", + "sprs", +] + [[package]] name = "mime" version = "0.3.17" @@ -5213,6 +5348,21 @@ dependencies = [ "syn", ] +[[package]] +name = "ndarray" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c7c9125e8f6f10c9da3aad044cc918cf8784fa34de857b1aa68038eb05a50a9" +dependencies = [ + "matrixmultiply", + "num-complex", + "num-integer", + "num-traits", + "portable-atomic", + "portable-atomic-util", + "rawpointer", +] + [[package]] name = "ndk" version = "0.9.0" @@ -7007,6 +7157,16 @@ dependencies = [ "syn", ] +[[package]] +name = "serde_flexitos" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3323d093d7597660758b742dd7a1525539613f6182b306a4e1dd6e01a89bada9" +dependencies = [ + "erased-serde", + "serde", +] + [[package]] name = "serde_ignored" version = "0.1.14" @@ -7300,6 +7460,18 @@ dependencies = [ "bitflags 2.10.0", ] +[[package]] +name = "sprs" +version = "0.11.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dca58a33be2188d4edc71534f8bafa826e787cc28ca1c47f31be3423f0d6e55" +dependencies = [ + "ndarray", + "num-complex", + "num-traits", + "smallvec", +] + [[package]] name = "stable_deref_trait" version = "1.2.1" @@ -7329,6 +7501,8 @@ dependencies = [ "console_error_panic_hook", "ctrlc", "getrandom 0.3.4", + "leafwing-input-manager", + "microlp", "ordered-float 5.1.0", "pico-args", "rand 0.9.2", @@ -8257,6 +8431,12 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" +[[package]] +name = "vec_map" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" + [[package]] name = "version_check" version = "0.9.5" diff --git a/crates/launcher/Cargo.toml b/crates/launcher/Cargo.toml index fb712aefbc7d353f74b07427090279195be5521c..a009af32e4d143267d3d9e19c9a7b599bbc89cc2 100644 --- a/crates/launcher/Cargo.toml +++ b/crates/launcher/Cargo.toml @@ -7,4 +7,4 @@ edition = "2024" eframe = "0.32" egui = "0.32" egui_extras = { version = "0.32", features = ["all_loaders"] } -starkingdoms = { path = "../unified", features = ["native"] } \ No newline at end of file +#starkingdoms = { path = "../unified", features = ["native"] } \ No newline at end of file diff --git a/crates/unified/Cargo.toml b/crates/unified/Cargo.toml index 8aef0565c63d3d8a5c060ab613d65351b5f8533a..14b85c3b89707545ae41e850c3a3b36b89fe7a05 100644 --- a/crates/unified/Cargo.toml +++ b/crates/unified/Cargo.toml @@ -31,7 +31,8 @@ bevy = { version = "0.17", default-features = false, features = [ "bevy_anti_alias", "bevy_sprite_render", "bevy_ui_render", - "zstd_rust" + "zstd_rust", + "debug" ] } avian2d = { version = "0.4", default-features = false, features = [ @@ -73,6 +74,9 @@ pico-args = "0.5" bevy_egui = { version = "0.38", optional = true } +leafwing-input-manager = { version = "0.19", optional = true } +microlp = { version = "0.2", optional = true } + [build-dependencies] built = { version = "0.8", features = ["git2", "chrono"] } @@ -97,4 +101,7 @@ wasm = ["getrandom/wasm_js", "bevy/webgl2"] particle_editor = ["bevy_egui"] server = ["aeronet_websocket/server", "aeronet_replicon/server"] -client = [] \ No newline at end of file +client = [ + "leafwing-input-manager", + "microlp" +] \ No newline at end of file diff --git a/crates/unified/assets/config/parts/hearty.part.toml b/crates/unified/assets/config/parts/hearty.part.toml index aa52e9d2b136eb4a6d164294580e8d56b3ef4e0a..51ad38f8638bb9bbd6f2efd2ab04c87e6ceffe43 100644 --- a/crates/unified/assets/config/parts/hearty.part.toml +++ b/crates/unified/assets/config/parts/hearty.part.toml @@ -10,8 +10,23 @@ mass = 100 [[thruster]] id = "bottom left" -apply_force_at_local = [ -50.0, -50.0 ] -thrust_vector = [ 0.0, 25.0 ] +apply_force_at_local = [ -25.0, -25.0 ] +thrust_vector = [ 0.0, 20.0 ] + +[[thruster]] +id = "bottom right" +apply_force_at_local = [ 25.0, -25.0 ] +thrust_vector = [ 0.0, 20.0 ] + +[[thruster]] +id = "top left" +apply_force_at_local = [ -25.0, 25.0 ] +thrust_vector = [ 0.0, -20.0 ] + +[[thruster]] +id = "top right" +apply_force_at_local = [ 25.0, 25.0 ] +thrust_vector = [ 0.0, -20.0 ] [[joint]] id = "Top" diff --git a/crates/unified/assets/config/parts/housing.part.toml b/crates/unified/assets/config/parts/housing.part.toml index d724dcb93556382169d9c2c27ea00a653a5d4773..3a3c943933fa99e5fc93b2c0256a60418986232a 100644 --- a/crates/unified/assets/config/parts/housing.part.toml +++ b/crates/unified/assets/config/parts/housing.part.toml @@ -8,6 +8,10 @@ width = 50 height = 50 mass = 50 +[[thruster]] +id = "primary" +thrust_vector = [ 0.0, 50.0 ] + [[joint]] id = "Top" target = { translation = [ 0.0, 55.0, 0.0 ], rotation = 0.0 } diff --git a/crates/unified/src/attachment.rs b/crates/unified/src/attachment.rs index ab51bb9239c6355434b10fb13786576d9818b363..50107cd604dbe58b177c214436e35044a3afbd1a 100644 --- a/crates/unified/src/attachment.rs +++ b/crates/unified/src/attachment.rs @@ -7,7 +7,7 @@ use std::ops::Deref; /// The primary component for a ship structure pub struct Ship; -#[derive(Component, Serialize, Deserialize, MapEntities)] +#[derive(Component, Serialize, Deserialize, MapEntities, Deref)] #[relationship_target(relationship = PartInShip, linked_spawn)] pub struct Parts(#[entities] Vec); diff --git a/crates/unified/src/client/input/mod.rs b/crates/unified/src/client/input/mod.rs index c6e8cd0bb2c1ca881ef0320286c022e455e6bdbe..82e14420075d75eeef055c62e406ae7113028a5d 100644 --- a/crates/unified/src/client/input/mod.rs +++ b/crates/unified/src/client/input/mod.rs @@ -1,16 +1,48 @@ +pub mod util; + use bevy::app::{App, Update}; use bevy::window::PrimaryWindow; -use crate::ecs::MainCamera; +use leafwing_input_manager::Actionlike; +use leafwing_input_manager::input_map::InputMap; +use leafwing_input_manager::prelude::{ActionState, ButtonlikeChord}; +use crate::ecs::{MainCamera, Me}; use crate::prelude::*; +#[derive(Actionlike, PartialEq, Eq, Hash, Clone, Copy, Debug, Reflect)] +pub enum ClientAction { + ThrustForward, + ThrustBackward, + ThrustRight, + ThrustLeft, + TorqueCw, + TorqueCcw +} pub fn input_plugin(app: &mut App) { app .insert_resource(CursorWorldCoordinates(None)) + .insert_resource(InputMap::new([ + (ClientAction::ThrustForward, KeyCode::KeyW), + (ClientAction::ThrustForward, KeyCode::ArrowUp), + + (ClientAction::ThrustBackward, KeyCode::KeyS), + (ClientAction::ThrustBackward, KeyCode::ArrowDown), + + (ClientAction::ThrustLeft, KeyCode::KeyQ), + // TODO: Shift+left + (ClientAction::ThrustRight, KeyCode::KeyE), + // TODO: Shift+right + + (ClientAction::TorqueCw, KeyCode::KeyD), + (ClientAction::TorqueCw, KeyCode::ArrowRight), + + (ClientAction::TorqueCcw, KeyCode::KeyA), + (ClientAction::TorqueCcw, KeyCode::ArrowLeft), + ])) + .init_resource::>() .add_systems(Update, update_cursor_position); } - #[derive(Resource, Default)] pub struct CursorWorldCoordinates(pub Option); diff --git a/crates/unified/src/client/input/util.rs b/crates/unified/src/client/input/util.rs new file mode 100644 index 0000000000000000000000000000000000000000..0bb96b2ee2fd18453f0bfe96e9e7b6f87398fb46 --- /dev/null +++ b/crates/unified/src/client/input/util.rs @@ -0,0 +1,11 @@ +use leafwing_input_manager::Actionlike; +use leafwing_input_manager::prelude::ActionState; + +pub trait ActionStateExt { + fn button_changed(&self, a: &A) -> bool; +} +impl ActionStateExt for ActionState { + fn button_changed(&self, a: &A) -> bool { + self.just_pressed(a) || self.just_released(a) + } +} \ No newline at end of file diff --git a/crates/unified/src/client/key_input.rs b/crates/unified/src/client/key_input.rs index bb8bafd62ad3027b5974a9be5419c5179cc60331..e8b85ab4dd12b160b16bb40dfdbf7c41a01746e0 100644 --- a/crates/unified/src/client/key_input.rs +++ b/crates/unified/src/client/key_input.rs @@ -8,9 +8,10 @@ use bevy::{ }; use std::ops::Deref; use crate::client::ship::attachment::AttachmentDebugRes; +use crate::client::ship::thrusters::ThrusterDebugRes; pub fn key_input_plugin(app: &mut App) { - app.add_systems(Update, directional_keys) + app .add_systems(Update, debug_render_keybind) .init_resource::(); } @@ -30,6 +31,7 @@ fn debug_render_keybind( keys: Res>, mut picking_debug_mode: ResMut, mut attachment_debug: ResMut, + mut thruster_debug: ResMut, ) { if keys.just_pressed(KeyCode::F4) { *picking_debug_mode = DebugPickingMode::Noisy; @@ -37,31 +39,9 @@ fn debug_render_keybind( if keys.just_pressed(KeyCode::F5) { attachment_debug.0 = !attachment_debug.0; } -} - -fn directional_keys(keys: Res>, mut thrust_event: MessageWriter) { - if keys.just_pressed(KeyCode::KeyW) || keys.just_pressed(KeyCode::ArrowUp) { - thrust_event.write(ThrustEvent::Up(true)); - } else if keys.just_released(KeyCode::KeyW) || keys.just_released(KeyCode::ArrowUp) { - thrust_event.write(ThrustEvent::Up(false)); - } - - if keys.just_pressed(KeyCode::KeyS) || keys.just_pressed(KeyCode::ArrowDown) { - thrust_event.write(ThrustEvent::Down(true)); - } else if keys.just_released(KeyCode::KeyS) || keys.just_released(KeyCode::ArrowDown) { - thrust_event.write(ThrustEvent::Down(false)); - } - - if keys.just_pressed(KeyCode::KeyA) || keys.just_pressed(KeyCode::ArrowLeft) { - thrust_event.write(ThrustEvent::Left(true)); - } else if keys.just_released(KeyCode::KeyA) || keys.just_released(KeyCode::ArrowLeft) { - thrust_event.write(ThrustEvent::Left(false)); - } - - if keys.just_pressed(KeyCode::KeyD) || keys.just_pressed(KeyCode::ArrowRight) { - thrust_event.write(ThrustEvent::Right(true)); - } else if keys.just_released(KeyCode::KeyD) || keys.just_released(KeyCode::ArrowRight) { - thrust_event.write(ThrustEvent::Right(false)); + if keys.just_pressed(KeyCode::F6) { + thruster_debug.0 = !thruster_debug.0; } } + diff --git a/crates/unified/src/client/mod.rs b/crates/unified/src/client/mod.rs index 78be4cce4acc3ae167c318d23703509bbdf30b80..0ce29aad4c8a09764f77a441bc59117fa5b6d2da 100644 --- a/crates/unified/src/client/mod.rs +++ b/crates/unified/src/client/mod.rs @@ -22,8 +22,8 @@ pub mod starfield; pub mod ui; pub mod zoom; pub mod ship; -mod rendering; -mod input; +pub mod rendering; +pub mod input; pub struct ClientPlugin { pub server: Option, @@ -43,6 +43,7 @@ impl Plugin for ClientPlugin { }) .add_plugins(rendering::render_plugin) .add_plugins(input::input_plugin) + .add_plugins(ship::thrusters::client_thrusters_plugin) .add_systems(Update, find_me) .add_systems(Update, net::set_config) .add_plugins((incoming_planets_plugin, indicators_plugin)) diff --git a/crates/unified/src/client/ship/mod.rs b/crates/unified/src/client/ship/mod.rs index 39005b9cd6df72a6ec5fab64e7f811eaea94e69c..14edaa489f8f1a161deac3645463bb539b62415a 100644 --- a/crates/unified/src/client/ship/mod.rs +++ b/crates/unified/src/client/ship/mod.rs @@ -1 +1,2 @@ -pub mod attachment; \ No newline at end of file +pub mod attachment; +pub mod thrusters; \ No newline at end of file diff --git a/crates/unified/src/client/ship/thrusters.rs b/crates/unified/src/client/ship/thrusters.rs new file mode 100644 index 0000000000000000000000000000000000000000..9afe3068f3369d1fd3b1ee432dea743ffff8aa7a --- /dev/null +++ b/crates/unified/src/client/ship/thrusters.rs @@ -0,0 +1,204 @@ +use std::collections::BTreeSet; +use std::time::Instant; +use bevy::app::App; +use bevy::color::palettes::basic::WHITE; +use bevy::color::palettes::css::LIMEGREEN; +use bevy::math::Vec3Swizzles; +use leafwing_input_manager::prelude::ActionState; +use microlp::{OptimizationDirection, Problem}; +use crate::attachment::Parts; +use crate::client::input::ClientAction; +use crate::ecs::thruster::{PartThrusters, Thruster, ThrusterOfPart}; +use crate::prelude::*; +use crate::client::input::util::ActionStateExt; +use crate::ecs::Me; +use crate::thrust::ThrustSolution; + +pub fn client_thrusters_plugin(app: &mut App) { + app + .insert_resource(ThrusterDebugRes(false)) + .insert_resource(ThrustSolution { + thrusters_on: BTreeSet::default() + }) + .add_systems(Update, draw_thruster_debug) + .add_systems(Update, solve_thrust); +} + +#[derive(Resource, Deref)] +pub struct ThrusterDebugRes(pub bool); + +fn draw_thruster_debug( + thruster_debug_res: Res, + thrusters: Query<(&Thruster, Entity, &GlobalTransform)>, + thrust_solution: Res, + mut gizmos: Gizmos, +) { + if !thruster_debug_res.0 { return }; + for thruster in thrusters { + // Draw white if it's just a thruster, bright green if it's in the current thrust solution + let color = if thrust_solution.thrusters_on.contains(&thruster.1) { + LIMEGREEN + } else { + WHITE + }; + gizmos.arrow_2d( + thruster.2.translation().xy(), + thruster.2.translation().xy() + thruster.2.rotation().mul_vec3(thruster.0.thrust_vector.extend(0.0)).xy(), + color + ); + } +} + +fn solve_thrust( + me: Query<(Option<&Parts>, &GlobalTransform, Entity), With>, + parts: Query<&PartThrusters>, + thrusters: Query<(&Thruster, &GlobalTransform)>, + input: Res>, + mut solution: ResMut, +) { + if !( + input.button_changed(&ClientAction::ThrustForward) + || input.button_changed(&ClientAction::ThrustBackward) + || input.button_changed(&ClientAction::TorqueCw) + || input.button_changed(&ClientAction::TorqueCcw) + || input.button_changed(&ClientAction::ThrustRight) + || input.button_changed(&ClientAction::ThrustLeft) + ) { return; /* no changes, existing thrust solution is valid */ } + + debug!("input changed, recalculating thrust solution"); + let start = Instant::now(); + + // determine our target vector: + // unit vector in the intended direction of movement + + // Z-axis torque: this cursed thing is apparently standard + // +Z == counterclockwise/ccw + // -Z == clockwise/cw + + let mut target_unit_vector = Vec3::ZERO; + + if input.pressed(&ClientAction::ThrustForward) { + target_unit_vector += Vec3::new(0.0, -1.0, 0.0); + } + if input.pressed(&ClientAction::ThrustBackward) { + target_unit_vector += Vec3::new(0.0, 1.0, 0.0); + } + if input.pressed(&ClientAction::ThrustRight) { + target_unit_vector += Vec3::new(-1.0, 0.0, 0.0); + } + if input.pressed(&ClientAction::ThrustLeft) { + target_unit_vector += Vec3::new(1.0, 0.0, 0.0); + } + if input.pressed(&ClientAction::TorqueCw) { + target_unit_vector += Vec3::new(0.0, 0.0, -1.0); + } + if input.pressed(&ClientAction::TorqueCcw) { + target_unit_vector += Vec3::new(0.0, 0.0, 1.0); + } + + if target_unit_vector == Vec3::ZERO { + debug!("no buttons are pressed; zeroing thrust solution"); + solution.thrusters_on.clear(); + debug!("solved thrust in {}ms", start.elapsed().as_millis()); + return; + } + + let Ok((our_parts, hearty_transform, hearty)) = me.single() else { + error!("could not solve for thrust: hearty does not exist?"); + error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); + return; + }; + let mut all_parts = vec![hearty]; + if let Some(parts) = our_parts { + all_parts.extend(parts.iter()); + } + + // collect all thrusters on our ship, and figure out their thrust vectors + let mut all_thrusters = vec![]; + + for part in &all_parts { + let Ok(part_thrusters) = parts.get(*part) else { + warn!("issue while solving for thrust: part {:?} has no thrusters? skipping...", *part); + continue; + }; + for thruster_id in &**part_thrusters { + let Ok((thruster, thruster_transform)) = thrusters.get(*thruster_id) else { + warn!("issue while solving for thrust: thruster {:?} of part {:?} does not exist? skipping...", *thruster_id, *part); + continue; + }; + + // determine the thruster force in world space + let thruster_vector = thruster_transform.rotation().mul_vec3(thruster.thrust_vector.extend(0.0)).xy(); + + // determine our xy offset from hearty + let relative_translation = thruster_transform.translation().xy() - hearty_transform.translation().xy(); + // determine our rotational offset from hearty + let relative_rotation = thruster_transform.rotation() * -hearty_transform.rotation(); + + let thruster_torque = relative_translation.extend(0.0).cross(thruster_vector.extend(0.0)).z; + + // magically assemble the vector! + let target_vector = thruster_vector.extend(thruster_torque); + + all_thrusters.push((thruster_id, target_vector)); + } + } + + // calculate thrust and torque values + debug!("found {} thrusters, computing coefficients", all_thrusters.len()); + + let coefficients = all_thrusters.iter() + .map(|u| target_unit_vector.dot(u.1)) + .collect::>(); + + debug!("preparing model"); + let mut problem = Problem::new(OptimizationDirection::Maximize); + + // add variables to problem + let variables = coefficients.iter() + .map(|u| problem.add_binary_var(*u as f64)) + .collect::>(); + + debug!("prepared {} variables; solving", variables.len()); + + let ssolution = match problem.solve() { + Ok(soln) => soln, + Err(e) => { + match e { + microlp::Error::Infeasible => { + error!("failed to solve for thrust: constraints cannot be satisfied"); + error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); + return; + }, + microlp::Error::Unbounded => { + error!("failed to solve for thrust: system is unbounded"); + error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); + return; + }, + microlp::Error::InternalError(e) => { + error!("failed to solve for thrust: solver encountered internal error: {e}"); + error!("failed to solve for thrust after {}ms", start.elapsed().as_millis()); + return; + } + } + } + }; + + debug!("found thrust solution!"); + debug!("solution alignment (higher is better): {}", ssolution.objective()); + + let mut new_soln = ThrustSolution { + thrusters_on: BTreeSet::default() + }; + + for thruster in all_thrusters.iter().enumerate() { + debug!("solution: thruster #{} ({:?}): {}", thruster.0, thruster.1.0, ssolution.var_value_rounded(variables[thruster.0])); + if ssolution.var_value_rounded(variables[thruster.0]) == 1.0 { + new_soln.thrusters_on.insert(*thruster.1.0); + } + } + + debug!("found thrust solution in {}ms", start.elapsed().as_millis()); + *solution = new_soln; + return; +} \ No newline at end of file diff --git a/crates/unified/src/client_plugins.rs b/crates/unified/src/client_plugins.rs index 6425b9d141c4ae5f450bd4c294ada2074309a624..17e2cfd8fe132157ed5b841e7b3e1d8221aa636a 100644 --- a/crates/unified/src/client_plugins.rs +++ b/crates/unified/src/client_plugins.rs @@ -6,6 +6,7 @@ use bevy::dev_tools::picking_debug::DebugPickingPlugin; use bevy::ecs::schedule::ScheduleLabel; use crate::prelude::*; use bevy::ui::UiPlugin; +use leafwing_input_manager::plugin::InputManagerPlugin; pub struct ClientPluginGroup { pub server: Option, @@ -21,6 +22,7 @@ impl PluginGroup for ClientPluginGroup { server: self.server, }) .add(UiPlugin) + .add(InputManagerPlugin::::default()) } } diff --git a/crates/unified/src/main.rs b/crates/unified/src/main.rs index 9b982d41885884633d32baac5d8e5e22a7c68f23..b7e0b01730a9ba987383fbef30098b4bade87262 100644 --- a/crates/unified/src/main.rs +++ b/crates/unified/src/main.rs @@ -42,6 +42,7 @@ pub mod prelude; pub mod wasm_entrypoint; mod cli; mod build_meta; +mod thrust; use std::str::FromStr; #[cfg(target_arch = "wasm32")] diff --git a/crates/unified/src/server/mod.rs b/crates/unified/src/server/mod.rs index 8183ebe829b177db65308a19060bffb7ff17f750..827e622ee6ee3ee68a1d49596caf13a596e430ec 100644 --- a/crates/unified/src/server/mod.rs +++ b/crates/unified/src/server/mod.rs @@ -5,7 +5,6 @@ pub mod planets; pub mod player; mod system_sets; mod world_config; -mod thruster; use crate::server::earth_parts::spawn_parts_plugin; use crate::server::gravity::newtonian_gravity_plugin; diff --git a/crates/unified/src/server/thruster/mod.rs b/crates/unified/src/server/thruster/mod.rs deleted file mode 100644 index 212f0965ef35816c229242b610b8ab4a2eeb05ea..0000000000000000000000000000000000000000 --- a/crates/unified/src/server/thruster/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! # Thruster solver -//! Given a ship and the desired target vector, solve for the combination of thrusters -//! that will produce as close to the desired target as possible. \ No newline at end of file diff --git a/crates/unified/src/thrust.rs b/crates/unified/src/thrust.rs new file mode 100644 index 0000000000000000000000000000000000000000..6f05e4b836e9e7088c8d778b285a26e00d53ec7f --- /dev/null +++ b/crates/unified/src/thrust.rs @@ -0,0 +1,11 @@ +use std::collections::BTreeSet; +use bevy::prelude::{Entity, Resource}; +use serde::{Deserialize, Serialize}; + +/// A thrust solution, found by the thrust solver on the client. +/// `thrusters_on` is the set of thrusters that should be on. +/// Any thrusters not in this set should be off. +#[derive(Serialize, Deserialize, Eq, PartialEq, Debug, Clone, Resource)] +pub struct ThrustSolution { + pub thrusters_on: BTreeSet +} \ No newline at end of file diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index 271800cb2f3791b3adc24328e71c9e2550b439db..0000000000000000000000000000000000000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly" \ No newline at end of file