M .cargo/config.toml => .cargo/config.toml +1 -1
@@ 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"']
M Cargo.lock => Cargo.lock +181 -1
@@ 1189,6 1189,22 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 1321,6 1337,7 @@ dependencies = [
"bevy_dev_tools",
"bevy_diagnostic",
"bevy_ecs",
+ "bevy_gilrs",
"bevy_gizmos",
"bevy_image",
"bevy_input",
@@ 3163,6 3180,24 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 3978,6 4013,40 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 4649,6 4718,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 4821,7 4900,6 @@ dependencies = [
"eframe",
"egui 0.32.3",
"egui_extras",
- "starkingdoms",
]
[[package]]
@@ 4831,6 4909,34 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 4876,6 4982,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 4954,6 5070,15 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 5045,6 5170,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 5214,6 5349,21 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 7008,6 7158,16 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 7301,6 7461,18 @@ dependencies = [
]
[[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"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ 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",
@@ 8258,6 8432,12 @@ 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"
source = "registry+https://github.com/rust-lang/crates.io-index"
M crates/launcher/Cargo.toml => crates/launcher/Cargo.toml +1 -1
@@ 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
M crates/unified/Cargo.toml => crates/unified/Cargo.toml +9 -2
@@ 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
M crates/unified/assets/config/parts/hearty.part.toml => crates/unified/assets/config/parts/hearty.part.toml +17 -2
@@ 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"
M crates/unified/assets/config/parts/housing.part.toml => crates/unified/assets/config/parts/housing.part.toml +4 -0
@@ 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 }
M crates/unified/src/attachment.rs => crates/unified/src/attachment.rs +1 -1
@@ 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<Entity>);
M crates/unified/src/client/input/mod.rs => crates/unified/src/client/input/mod.rs +34 -2
@@ 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::<ActionState<ClientAction>>()
.add_systems(Update, update_cursor_position);
}
-
#[derive(Resource, Default)]
pub struct CursorWorldCoordinates(pub Option<Vec2>);
A crates/unified/src/client/input/util.rs => crates/unified/src/client/input/util.rs +11 -0
@@ 0,0 1,11 @@
+use leafwing_input_manager::Actionlike;
+use leafwing_input_manager::prelude::ActionState;
+
+pub trait ActionStateExt<A> {
+ fn button_changed(&self, a: &A) -> bool;
+}
+impl<A: Actionlike> ActionStateExt<A> for ActionState<A> {
+ fn button_changed(&self, a: &A) -> bool {
+ self.just_pressed(a) || self.just_released(a)
+ }
+}<
\ No newline at end of file
M crates/unified/src/client/key_input.rs => crates/unified/src/client/key_input.rs +6 -26
@@ 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::<PhysicsDebugRes>();
}
@@ 30,6 31,7 @@ fn debug_render_keybind(
keys: Res<ButtonInput<KeyCode>>,
mut picking_debug_mode: ResMut<DebugPickingMode>,
mut attachment_debug: ResMut<AttachmentDebugRes>,
+ mut thruster_debug: ResMut<ThrusterDebugRes>,
) {
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<ButtonInput<KeyCode>>, mut thrust_event: MessageWriter<ThrustEvent>) {
- 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;
}
}
+
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +3 -2
@@ 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<String>,
@@ 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))
M crates/unified/src/client/ship/mod.rs => crates/unified/src/client/ship/mod.rs +2 -1
@@ 1,1 1,2 @@
-pub mod attachment;>
\ No newline at end of file
+pub mod attachment;
+pub mod thrusters;<
\ No newline at end of file
A crates/unified/src/client/ship/thrusters.rs => crates/unified/src/client/ship/thrusters.rs +204 -0
@@ 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<ThrusterDebugRes>,
+ thrusters: Query<(&Thruster, Entity, &GlobalTransform)>,
+ thrust_solution: Res<ThrustSolution>,
+ 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<Me>>,
+ parts: Query<&PartThrusters>,
+ thrusters: Query<(&Thruster, &GlobalTransform)>,
+ input: Res<ActionState<ClientAction>>,
+ mut solution: ResMut<ThrustSolution>,
+) {
+ 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::<Vec<_>>();
+
+ 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::<Vec<_>>();
+
+ 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
M crates/unified/src/client_plugins.rs => crates/unified/src/client_plugins.rs +2 -0
@@ 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<String>,
@@ 21,6 22,7 @@ impl PluginGroup for ClientPluginGroup {
server: self.server,
})
.add(UiPlugin)
+ .add(InputManagerPlugin::<crate::client::input::ClientAction>::default())
}
}
M crates/unified/src/main.rs => crates/unified/src/main.rs +1 -0
@@ 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")]
M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +0 -1
@@ 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;
D crates/unified/src/server/thruster/mod.rs => crates/unified/src/server/thruster/mod.rs +0 -3
@@ 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
A crates/unified/src/thrust.rs => crates/unified/src/thrust.rs +11 -0
@@ 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<Entity>
+}<
\ No newline at end of file
D rust-toolchain.toml => rust-toolchain.toml +0 -2
@@ 1,2 0,0 @@
-[toolchain]
-channel = "nightly">
\ No newline at end of file