M crates/unified/src/client/components/mod.rs => crates/unified/src/client/components/mod.rs +1 -37
@@ 1,49 1,13 @@
-use crate::prelude::{Component, Deserialize, GizmoConfigGroup, Handle, Image, Reflect, Resource, Serialize};
+use crate::prelude::Component;
#[derive(Component)]
pub struct MainCamera;
#[derive(Component)]
-pub struct StarguideCamera;
-
-#[derive(Component)]
-pub struct OrbitCamera;
-
-#[derive(Default, Reflect, GizmoConfigGroup)]
-pub struct StarguideGizmos;
-
-#[derive(Component)]
-pub struct StarfieldFront;
-
-#[derive(Component)]
-pub struct StarfieldMid;
-
-#[derive(Component)]
-pub struct StarfieldBack;
-
-#[derive(Component)]
pub struct FuelText;
#[derive(Component)]
pub struct PowerText;
#[derive(Component)]
-pub struct PlanetSensor(pub String);
-
-#[derive(Component)]
pub struct Me;
-
-#[derive(Component)]
-pub struct StarguideMe;
-
-#[derive(Resource)]
-pub struct StarguideOrbitImage(pub Handle<Image>);
-
-#[derive(Component)]
-pub struct StarguideOrbit;
-
-#[derive(Component, Serialize, Deserialize, Debug)]
-pub struct CraftingUi;
-
-#[derive(Component, Serialize, Deserialize, Debug)]
-pub struct TemperatureSprite;>
\ No newline at end of file
A crates/unified/src/client/crafting/components.rs => crates/unified/src/client/crafting/components.rs +4 -0
@@ 0,0 1,4 @@
+use crate::prelude::{Component, Deserialize, Serialize};
+
+#[derive(Component, Serialize, Deserialize, Debug)]
+pub struct CraftingUi;<
\ No newline at end of file
M crates/unified/src/client/crafting/mod.rs => crates/unified/src/client/crafting/mod.rs +1 -0
@@ 1,1 1,2 @@
pub mod ui;
+pub mod components;
M crates/unified/src/client/crafting/ui.rs => crates/unified/src/client/crafting/ui.rs +2 -1
@@ 3,7 3,8 @@ use std::collections::HashMap;
use bevy::{input_focus::AutoFocus, ui::RelativeCursorPosition};
use crate::{client::colors, prelude::*};
-use crate::client::components::{CraftingUi, MainCamera, Me};
+use crate::client::components::{MainCamera, Me};
+use crate::client::crafting::components::CraftingUi;
use crate::shared::attachment::PartInShip;
use crate::shared::config::recipe::RecipesConfig;
use crate::shared::ecs::{CanCraft, CraftPartRequest, Drill, Part, SingleStorage, ToggleDrillEvent, VariableStorage};
M crates/unified/src/client/mod.rs => crates/unified/src/client/mod.rs +1 -1
@@ 8,7 8,7 @@ use crate::client::ui::ui_plugin;
use crate::client::zoom::zoom_plugin;
use crate::client::starguide::init::starguide_init_plugin;
use crate::client::starguide::input::starguide_input_plugin;
-use components::StarguideGizmos;
+use starguide::components::StarguideGizmos;
use bevy::dev_tools::picking_debug::DebugPickingMode;
use crate::prelude::*;
use planet::incoming_planets::incoming_planets_plugin;
M crates/unified/src/client/parts.rs => crates/unified/src/client/parts.rs +49 -63
@@ 4,7 4,7 @@ use crate::shared::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf
use crate::client::crafting::ui::open_crafting_ui;
use crate::shared::ecs::Temperature;
use crate::client::colors::GREEN;
-use crate::shared::ecs::{DragRequestEvent, Part, MAIN_LAYER};
+use crate::shared::ecs::{DragAction, DragRequestEvent, Part, MAIN_LAYER};
use crate::client::input::CursorWorldCoordinates;
use bevy::color::palettes::css::{ORANGE, PURPLE, RED, YELLOW};
use crate::client::components::Me;
@@ 12,8 12,8 @@ use crate::client::ship::attachment::AttachmentDebugRes;
use crate::prelude::*;
pub fn parts_plugin(app: &mut App) {
- app.insert_resource(DragResource(None));
- app.insert_resource(SnapResource(None, None));
+ app.insert_resource(DragResource { dragged: None });
+ app.insert_resource(SnapResource { target: None, peer: None });
app.add_systems(
Update,
(
@@ 27,23 27,32 @@ pub fn parts_plugin(app: &mut App) {
app.add_observer(on_part_release);
}
+fn temp_to_color(temperature: &Temperature) -> Color {
+ Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0)
+}
+
+fn build_part_sprite(part: &Part, temperature: &Temperature, is_connected: bool, asset_server: &AssetServer) -> Sprite {
+ let path = if is_connected {
+ &part.strong_config.part.sprite_connected
+ } else {
+ &part.strong_config.part.sprite_disconnected
+ };
+ let mut sprite = Sprite::from_image(asset_server.load(path));
+ sprite.custom_size = Some(Vec2::new(
+ part.strong_config.physics.width as f32,
+ part.strong_config.physics.height as f32,
+ ));
+ sprite.color = temp_to_color(temperature);
+ sprite
+}
+
fn handle_incoming_parts(
mut commands: Commands,
new_parts: Query<(Entity, &Part, &Temperature, Option<&PartInShip>), Added<Part>>,
asset_server: Res<AssetServer>,
) {
for (new_entity, new_part, temperature, is_connected) in new_parts.iter() {
- let mut sprite = Sprite::from_image(asset_server.load(if is_connected.is_some() {
- &new_part.strong_config.part.sprite_connected
- } else {
- &new_part.strong_config.part.sprite_disconnected
- }));
- sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
- sprite.custom_size = Some(Vec2::new(
- new_part.strong_config.physics.width as f32,
- new_part.strong_config.physics.height as f32,
- ));
-
+ let sprite = build_part_sprite(new_part, temperature, is_connected.is_some(), &asset_server);
commands
.entity(new_entity)
.insert(MAIN_LAYER)
@@ 53,30 62,22 @@ fn handle_incoming_parts(
.observe(open_crafting_ui);
}
}
+
fn handle_updated_temperature(
mut updated_parts: Query<(&mut Sprite, &Temperature), (With<Part>, Changed<Temperature>)>
) {
for (mut sprite, temperature) in updated_parts.iter_mut() {
- sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
+ sprite.color = temp_to_color(temperature);
}
}
+
fn handle_updated_parts(
mut commands: Commands,
updated_parts: Query<(Entity, &Part, &Temperature, Option<&PartInShip>), Changed<Part>>,
asset_server: Res<AssetServer>,
) {
for (updated_entity, updated_part, temperature, is_connected) in updated_parts.iter() {
- let mut sprite = Sprite::from_image(asset_server.load(if is_connected.is_some() {
- &updated_part.strong_config.part.sprite_connected
- } else {
- &updated_part.strong_config.part.sprite_disconnected
- }));
- sprite.custom_size = Some(Vec2::new(
- updated_part.strong_config.physics.width as f32,
- updated_part.strong_config.physics.height as f32,
- ));
- sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
-
+ let sprite = build_part_sprite(updated_part, temperature, is_connected.is_some(), &asset_server);
commands
.entity(updated_entity)
.remove::<Sprite>()
@@ 95,37 96,23 @@ fn update_part_sprites(
let Ok((part, temperature, connected_to)) = parts.get(e) else {
continue;
};
-
- let sprite = if connected_to.is_some() {
- &part.strong_config.part.sprite_connected
- } else {
- &part.strong_config.part.sprite_disconnected
- };
-
- let mut sprite = Sprite::from_image(asset_server.load(sprite));
- sprite.custom_size = Some(Vec2::new(
- part.strong_config.physics.width as f32,
- part.strong_config.physics.height as f32,
- ));
- sprite.color = Color::srgb(1.0 + ((temperature.0 as f32 - 300.0) / 700.0).max(0.0), 1.0, 1.0);
- //sprite.color = Color::srgb(1.0, (700.0 - temperature.0) / 700.0, (700.0 - temperature.0) / 700.0);
-
+ let sprite = build_part_sprite(part, temperature, connected_to.is_some(), &asset_server);
commands.entity(e).insert(sprite);
}
}
#[derive(Resource)]
-struct DragResource(Option<Entity>);
+struct DragResource { dragged: Option<Entity> }
#[derive(Resource)]
-struct SnapResource(Option<Entity>, Option<Entity>);
+struct SnapResource { target: Option<Entity>, peer: Option<Entity> }
#[derive(Component)]
struct DragGhost;
#[derive(Component)]
struct Ghost {
pub rot: Quat,
- pub last_target_pos: Vec3,
- pub vel: Vec3
+ pub spring_pos: Vec3,
+ pub spring_vel: Vec3,
}
const ROTATION_SMOOTH: f32 = 0.1;
@@ 157,11 144,11 @@ fn on_part_click(
s.color = Color::srgba(0.7, 0.7, 0.7, 1.0);
commands.spawn((DragGhost, Ghost {
rot: sprite.1.rotation,
- last_target_pos: sprite.1.translation,
- vel: Vec3::ZERO,
+ spring_pos: sprite.1.translation,
+ spring_vel: Vec3::ZERO,
}, *sprite.1, s));
- drag.0 = Some(ev.event().event_target());
+ drag.dragged = Some(ev.event().event_target());
}
fn on_part_release(
@@ 177,7 164,7 @@ fn on_part_release(
return;
}
- if let Some(e) = drag.0 {
+ if let Some(e) = drag.dragged {
let rotation = ghost.1.rotation;
commands.entity(ghost.0).despawn();
@@ 185,15 172,15 @@ fn on_part_release(
debug!(?e, ?c, "sending drag request");
events.write(DragRequestEvent {
drag_target: e,
- drag_to: c,
- set_rotation: rotation,
- snap_target: snap.0,
- peer_snap: snap.1,
+ action: match (snap.target, snap.peer) {
+ (Some(snap_target), Some(peer_snap)) => DragAction::Attach { snap_target, peer_snap },
+ _ => DragAction::Free { position: c, rotation },
+ },
});
}
}
- drag.0 = None;
+ drag.dragged = None;
}
const F: f32 = 6.0; // frequency (Hz)
@@ 291,7 278,7 @@ fn update_drag_ghosts(
let mut best_self_position = None;
for (snap_local_transform, snap_joint, snap_part, snap_id) in &snaps {
- if Some(snap_part.0) == drag.0 {
+ if Some(snap_part.0) == drag.dragged {
continue;
}
@@ 355,7 342,7 @@ fn update_drag_ghosts(
let mut best_peer_target_pos = None;
let mut best_joint_transform = None;
for (our_snap_local_transform, our_snap_joint, our_snap_part, our_snap_id) in &snaps {
- if Some(our_snap_part.0) != drag.0 {
+ if Some(our_snap_part.0) != drag.dragged {
continue;
}
let our_snap_global_translation =
@@ 412,23 399,22 @@ fn update_drag_ghosts(
ghost_info.rot = best_target.rotation;
ghost.rotation = ghost.rotation.slerp(ghost_info.rot, ROTATION_SMOOTH);
- let target_vel = (best_target.translation - ghost_info.last_target_pos)
- /time.delta_secs();
+ let target_vel = (best_target.translation - ghost_info.spring_pos) / time.delta_secs();
//let a = (best_target.translation - ghost.translation
// + K3*target_vel - K1*ghost_info.vel)/K2;
- let (r_k1, v_k1, r_k2, v_k2, r_k3, v_k3, r_k4, v_k4) = rk4_k_values(best_target.translation, target_vel, ghost.translation, ghost_info.vel, time.delta_secs());
+ let (r_k1, v_k1, r_k2, v_k2, r_k3, v_k3, r_k4, v_k4) = rk4_k_values(best_target.translation, target_vel, ghost.translation, ghost_info.spring_vel, time.delta_secs());
ghost.translation += (r_k1 + 2.0*r_k2 + 2.0*r_k3 + r_k4)/6.0*time.delta_secs();
- ghost_info.vel += (v_k1 + 2.0*v_k2 + 2.0*v_k3 + v_k4)/6.0*time.delta_secs();
+ ghost_info.spring_vel += (v_k1 + 2.0*v_k2 + 2.0*v_k3 + v_k4)/6.0*time.delta_secs();
- ghost_info.last_target_pos = best_target.translation;
+ ghost_info.spring_pos = best_target.translation;
let partvel = linvel.single().unwrap();
ghost.translation.x += partvel.x as f32 * time.delta_secs();
ghost.translation.y += partvel.y as f32 * time.delta_secs();
- rsnap.0 = snap;
- rsnap.1 = best_self_snap;
+ rsnap.target = snap;
+ rsnap.peer = best_self_snap;
}
M crates/unified/src/client/rendering/mod.rs => crates/unified/src/client/rendering/mod.rs +2 -1
@@ 2,7 2,8 @@ use bevy::anti_alias::fxaa::Fxaa;
use bevy::app::{App, Startup};
use bevy::core_pipeline::tonemapping::DebandDither;
use bevy::post_process::bloom::Bloom;
-use crate::client::components::{MainCamera, Me, StarguideGizmos};
+use crate::client::components::{MainCamera, Me};
+use crate::client::starguide::components::StarguideGizmos;
use crate::shared::ecs::{GameplayState, MAIN_LAYER, STARGUIDE_LAYER};
use crate::prelude::*;
M crates/unified/src/client/ship/thrusters.rs => crates/unified/src/client/ship/thrusters.rs +14 -24
@@ 55,6 55,9 @@ fn draw_thruster_debug(
}
}
+const THRUSTER_CONTRIBUTION_EPSILON: f32 = 0.1;
+const THRUST_ACTIVATION_THRESHOLD: f64 = 0.8;
+
// TODO(core): replace goodlp with calling clarabel directly and cache models
/// The thrust solver!
/// This is an annoyingly complicated function...
@@ 203,7 206,7 @@ fn solve_thrust(
// Although all the numbers going in were normalized, the torque output is in different
// units and is wacky. Re-normalize it to a set of expected values, since this is all
// direction based anyway.
- let renormalized_thruster_torque = if thruster_torque.abs() < 0.1 {
+ let renormalized_thruster_torque = if thruster_torque.abs() < THRUSTER_CONTRIBUTION_EPSILON {
0.0 // This thruster's effect is small enough to be ignored
} else if thruster_torque < 0.0 {
-1.0 // if it's negative, force to -1
@@ 234,7 237,7 @@ fn solve_thrust(
// calculate thrust ~~and torque~~ values
/*
- Consult the paper for more information.
+ Consult the paper for more information. (https://typst.app/project/rGBlHvhGRntdfxOjzEL8EO)
Recall that we're optimizing an equation of form i_0 * x_0 + i_1 * x_1 + i_2 * x_2 ... i_n * x_n
"Coefficients" are i_0 ... i_n, and can be precomputed, and x_0 ... x_n is the "decision variables"
@@ 261,7 264,7 @@ fn solve_thrust(
// if thrust coefficient is <0.1, zap it entirely (this thruster is not helping)
// This is done elsewhere for torque, so pass it (u.1) through unchanged
// TODO(core): figure out how to make this adjustable
- if u.0.abs() < 0.1 {
+ if u.0.abs() < THRUSTER_CONTRIBUTION_EPSILON {
(0.0, u.1)
} else {
(u.0, u.1)
@@ 319,17 322,13 @@ fn solve_thrust(
}
};
- // did the solution converge?
- match thrust_solution.status() {
- SolutionStatus::Optimal => {}, // yay!
- SolutionStatus::TimeLimit => {
- warn!("thrust solver failed to converge, hit time limit");
- }
- SolutionStatus::GapLimit => {
- warn!("thrust solver failed to converge, hit gap limit");
- }
- }
+ let check_convergence = |status, label: &str| match status {
+ SolutionStatus::Optimal => {},
+ SolutionStatus::TimeLimit => warn!("{label} solver failed to converge, hit time limit"),
+ SolutionStatus::GapLimit => warn!("{label} solver failed to converge, hit gap limit"),
+ };
+ check_convergence(thrust_solution.status(), "thrust");
trace!("finished thrust solve @ {:?}", start.elapsed());
trace!("starting torque solve @ {:?}", start.elapsed());
@@ 342,16 341,7 @@ fn solve_thrust(
}
};
- // did the solution converge?
- match torque_solution.status() {
- SolutionStatus::Optimal => {}, // yay!
- SolutionStatus::TimeLimit => {
- warn!("torque solver failed to converge, hit time limit");
- }
- SolutionStatus::GapLimit => {
- warn!("torque solver failed to converge, hit gap limit");
- }
- }
+ check_convergence(torque_solution.status(), "torque");
trace!("finished torque solve @ {:?}ms", start.elapsed());
@@ 371,7 361,7 @@ fn solve_thrust(
// is above 80%.
// The solver seems to be picking 0.0 or 1.0 in all circumstances anyway, but just in case.
- if thrust_solution.value(variables[thruster.0].0.1) > 0.8 || torque_solution.value(variables[thruster.0].1.1) > 0.8 {
+ if thrust_solution.value(variables[thruster.0].0.1) > THRUST_ACTIVATION_THRESHOLD || torque_solution.value(variables[thruster.0].1.1) > THRUST_ACTIVATION_THRESHOLD {
new_soln.thrusters_on.insert(*thruster.1.0);
}
}
M crates/unified/src/client/starfield.rs => crates/unified/src/client/starfield.rs +2 -1
@@ 15,9 15,10 @@ use bevy::{
transform::components::Transform,
window::{Window, WindowResized},
};
-use crate::client::components::{MainCamera, StarfieldBack, StarfieldFront, StarfieldMid};
+use crate::client::components::MainCamera;
use crate::shared::ecs::MAIN_LAYER;
use crate::client::components::Me;
+use crate::client::starguide::components::{StarfieldBack, StarfieldFront, StarfieldMid};
pub const BACK_STARFIELD_SIZE: f32 = 256.0;
pub const MID_STARFIELD_SIZE: f32 = 384.0;
A crates/unified/src/client/starguide/components.rs => crates/unified/src/client/starguide/components.rs +25 -0
@@ 0,0 1,25 @@
+use crate::prelude::{Component, GizmoConfigGroup, Handle, Image, Reflect, Resource};
+
+#[derive(Component)]
+pub struct StarguideMe;
+
+#[derive(Resource)]
+pub struct StarguideOrbitImage(pub Handle<Image>);
+
+#[derive(Component)]
+pub struct StarguideOrbit;
+
+#[derive(Component)]
+pub struct StarguideCamera;
+
+#[derive(Default, Reflect, GizmoConfigGroup)]
+pub struct StarguideGizmos;
+
+#[derive(Component)]
+pub struct StarfieldFront;
+
+#[derive(Component)]
+pub struct StarfieldMid;
+
+#[derive(Component)]
+pub struct StarfieldBack;<
\ No newline at end of file
M crates/unified/src/client/starguide/init.rs => crates/unified/src/client/starguide/init.rs +2 -1
@@ 1,7 1,8 @@
use bevy::anti_alias::fxaa::Fxaa;
use bevy::core_pipeline::tonemapping::DebandDither;
use bevy::post_process::bloom::Bloom;
-use crate::client::components::{Me, StarguideCamera, StarguideMe, StarguideOrbit};
+use crate::client::components::Me;
+use crate::client::starguide::components::{StarguideCamera, StarguideMe, StarguideOrbit};
use crate::prelude::*;
use crate::shared::ecs::{Part, STARGUIDE_LAYER};
M crates/unified/src/client/starguide/input.rs => crates/unified/src/client/starguide/input.rs +1 -1
@@ 1,6 1,6 @@
use crate::prelude::*;
use crate::client::input::CursorWorldCoordinates;
-use crate::client::components::StarguideCamera;
+use crate::client::starguide::components::StarguideCamera;
pub fn starguide_input_plugin(app: &mut App) {
app
M crates/unified/src/client/starguide/mod.rs => crates/unified/src/client/starguide/mod.rs +1 -0
@@ 1,3 1,4 @@
pub mod init;
pub mod input;
pub mod orbit;
+pub mod components;
M crates/unified/src/client/starguide/orbit.rs => crates/unified/src/client/starguide/orbit.rs +2 -1
@@ 1,5 1,6 @@
use std::f64::consts::PI;
-use crate::client::components::{Me, StarguideCamera, StarguideGizmos};
+use crate::client::components::Me;
+use crate::client::starguide::components::{StarguideCamera, StarguideGizmos};
use crate::prelude::*;
use crate::shared::config::planet::Planet;
use crate::shared::world_config::WorldConfigResource;
M crates/unified/src/client/zoom.rs => crates/unified/src/client/zoom.rs +4 -4
@@ 2,8 2,9 @@ use bevy::{
input::mouse::{MouseScrollUnit, MouseWheel},
prelude::*,
};
-use crate::client::components::{MainCamera, Me, OrbitCamera, StarfieldBack, StarfieldFront, StarfieldMid, StarguideCamera};
+use crate::client::components::{MainCamera, Me};
use crate::client::starfield::{StarfieldSize, BACK_STARFIELD_SIZE, FRONT_STARFIELD_SIZE, MID_STARFIELD_SIZE};
+use crate::client::starguide::components::{StarfieldBack, StarfieldFront, StarfieldMid, StarguideCamera};
use crate::shared::ecs::GameplayState;
pub fn zoom_plugin(app: &mut App) {
app.add_systems(Update, on_scroll);
@@ 52,7 53,7 @@ fn on_scroll(
/*mut orbit_camera: Single<
&mut Camera,
(
- With<OrbitCamera>,
+
Without<StarguideCamera>,
Without<MainCamera>,
Without<Me>,
@@ 65,7 66,6 @@ fn on_scroll(
(&mut Camera, &mut Projection),
(
With<MainCamera>,
- Without<OrbitCamera>,
Without<StarguideCamera>,
Without<Me>,
Without<StarfieldFront>,
@@ 92,7 92,7 @@ fn on_scroll(
Without<StarfieldMid>,
Without<StarfieldBack>,
Without<MainCamera>,
- Without<OrbitCamera>,
+
Without<StarguideCamera>,
),
>,*/
A crates/unified/src/server/components/mod.rs => crates/unified/src/server/components/mod.rs +4 -0
@@ 0,0 1,4 @@
+use crate::prelude::Component;
+
+#[derive(Component)]
+pub struct PlanetSensor(pub String);<
\ No newline at end of file
M crates/unified/src/server/drill.rs => crates/unified/src/server/drill.rs +1 -1
@@ 1,4 1,4 @@
-use crate::client::components::PlanetSensor;
+use crate::server::components::PlanetSensor;
use crate::prelude::*;
use crate::shared::attachment::{PartInShip, Parts};
use crate::shared::config::planet::Planet;
M crates/unified/src/server/mod.rs => crates/unified/src/server/mod.rs +1 -0
@@ 10,6 10,7 @@ pub mod player;
mod system_sets;
pub mod orbit;
pub mod plugins;
+pub mod components;
use crate::server::craft::craft_plugin;
use crate::server::damping::damping_plugin;
M crates/unified/src/server/planets.rs => crates/unified/src/server/planets.rs +1 -1
@@ 1,4 1,4 @@
-use crate::client::components::PlanetSensor;
+use crate::server::components::PlanetSensor;
use bevy::{asset::Handle, math::DVec3};
use crate::prelude::*;
use crate::shared::config::planet::{PlanetSpring, PlanetSpringJoint};
M crates/unified/src/server/player.rs => crates/unified/src/server/player.rs +130 -163
@@ 2,7 2,7 @@ pub mod join;
pub mod thrust;
use crate::shared::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint};
-use crate::shared::ecs::{DragRequestEvent, Part, Player, PlayerStorage};
+use crate::shared::ecs::{DragAction, DragRequestEvent, Part, Player, PlayerStorage};
use crate::server::damping::ModuleJointDamping;
use crate::server::system_sets::PlayerInputSet;
use crate::prelude::*;
@@ 50,25 50,24 @@ fn complete_partial_disconnects(
// is it still connected to hearty?
let mut search_visited_joints = vec![];
- let can_reach_hearty = can_reach_hearty(
+ let connected = is_connected_to_hearty(
partially_disconnected_part,
q_joints.reborrow(),
q_maybe_peer.reborrow(),
q_joint_of_part.reborrow(),
q_is_hearty.reborrow(),
&mut search_visited_joints,
- true
);
- if can_reach_hearty {
+ if connected {
// great, leave them alone
continue;
}
- // this part cannot reach hearty
- // trigger a disconnect on them to propagate the disconnection
+ debug!("partial detach DFS: visited {} joints => not connected", search_visited_joints.len());
+ // this part cannot reach hearty; propagate the disconnection
let mut disconnect_queue = vec![];
commands.entity(partially_disconnected_part).remove::<PartInShip>(); // they're no longer in the ship, remove them from the meta
- disconnect_part(
+ propagate_disconnect(
(partially_disconnected_part, if let Ok(j) = q_joints.get(partially_disconnected_part) { j } else {
warn!(?partially_disconnected_part, "part does not have a Joints? this should be impossible...");
continue;
@@ 82,10 81,10 @@ fn complete_partial_disconnects(
}
}
-/// Determine if a part has a path to hearty by performing a depth-first search
+/// Determine if a part is connected to hearty by performing a depth-first search.
/// TODO: This can be very slow on large ships- the path propagation model will be significantly faster
/// TODO: Ask core for an explanation of what the path propagation model is if you want to implement this
-fn can_reach_hearty(
+fn is_connected_to_hearty(
part: Entity,
mut q_joints: Query<&Joints>,
@@ 94,8 93,6 @@ fn can_reach_hearty(
mut q_is_hearty: Query<Option<&Player>>,
visited_joints: &mut Vec<Entity>,
-
- is_top_of_recursion: bool
) -> bool {
// Are we hearty?
if let Ok(Some(_)) = q_is_hearty.get(part) {
@@ 138,19 135,16 @@ fn can_reach_hearty(
debug!("-> via {:?}", part);
return true;
}
- // not hearty. but can the other part reach hearty?
- let can_other_part_reach = can_reach_hearty(
+ // not hearty, but can the other part reach hearty?
+ let other_connected = is_connected_to_hearty(
other_part,
q_joints.reborrow(),
q_maybe_peer.reborrow(),
q_joint_of_part.reborrow(),
q_is_hearty.reborrow(),
visited_joints,
- false
);
- if can_other_part_reach {
- // great, they are connected
- // log that we're in the path, then bubble up
+ if other_connected {
debug!("-> via {:?}", part);
return true;
}
@@ 158,17 152,12 @@ fn can_reach_hearty(
}
}
- // Exhausted all options; we are not connected to hearty bubble up
- if is_top_of_recursion {
- // print a debug message
- debug!("partial detach DFS: visited {} joints => not connected", visited_joints.len());
- }
false
}
-fn disconnect_part(
+fn propagate_disconnect(
(entity, joints): (Entity, &Joints),
q_joints: &Query<(&Joint, &JointOf, &Transform, Option<&Peer>, Entity)>,
q_only_joints: &Query<&Joints>,
@@ 206,7 195,7 @@ fn disconnect_part(
//commands.entity(other_joint_of.0).remove::<PartInShip>();
if !processed_peers.contains(&peer.peer_joint_entity_id) {
- disconnect_part((other_joint_of.0, joints), q_joints,
+ propagate_disconnect((other_joint_of.0, joints), q_joints,
q_only_joints, processed_peers, commands.reborrow());
}
@@ 251,160 240,138 @@ fn dragging(
let mut new_linvel = None;
let mut new_angvel = None;
- if let Some(snap_to) = event.snap_target
- && let Some(peer_snap) = event.peer_snap
- {
- let Ok(snap_on_target) = snaps.get(snap_to) else {
- continue;
- };
- let Ok(snap_on_source) = snaps.get(peer_snap) else {
- continue;
- };
+ match &event.action {
+ DragAction::Attach { snap_target, peer_snap } => {
+ let Ok((target_snap_part, target_snap_joint)) = snaps.get(*snap_target) else {
+ continue;
+ };
+ let Ok((source_snap_part, source_snap_joint)) = snaps.get(*peer_snap) else {
+ continue;
+ };
- let Ok(target_joint) = joints.get(snap_on_target.1.0) else {
- continue;
- };
- let Ok(source_joint) = joints.get(snap_on_source.1.0) else {
- continue;
- };
+ let Ok((target_jt, target_jt_of, target_jt_xform, target_jt_peer, target_jt_id)) = joints.get(target_snap_joint.0) else {
+ continue;
+ };
+ let Ok((source_jt, source_jt_of, _source_jt_xform, source_jt_peer, source_jt_id)) = joints.get(source_snap_joint.0) else {
+ continue;
+ };
- // validation step 1: everything must match. if not, ignore the request
- if snap_on_target.0.0 != target_joint.1.0 {
- warn!(
- "drag request: mismatched target entities (potential manipulation?), ignoring"
- );
- continue;
- }
- if snap_on_source.0.0 != source_joint.1.0 {
- warn!(
- "drag request: mismatched source entities (potential manipulation?), ignoring request"
- );
- continue;
- }
+ // validation: snap ownership must agree with joint ownership
+ if target_snap_part.0 != target_jt_of.0 {
+ warn!("drag request: mismatched target entities (potential manipulation?), ignoring");
+ continue;
+ }
+ if source_snap_part.0 != source_jt_of.0 {
+ warn!("drag request: mismatched source entities (potential manipulation?), ignoring request");
+ continue;
+ }
- // we've passed initial validation.
- // do not allow drags with the source or destination if they already have a peer (are attached)
- if target_joint.3.is_some() {
- warn!(
- "drag request: cannot attach to a joint that already has a peer, ignoring request"
- );
- continue;
- }
- if source_joint.3.is_some() {
- warn!(
- "drag request: dragging from a part that is already attached is currently not supported, ignoring request"
- );
- continue;
- }
+ // do not allow drags where either joint already has a peer (is attached)
+ if target_jt_peer.is_some() {
+ warn!("drag request: cannot attach to a joint that already has a peer, ignoring request");
+ continue;
+ }
+ if source_jt_peer.is_some() {
+ warn!("drag request: dragging from a part that is already attached is currently not supported, ignoring request");
+ continue;
+ }
- // great, the attachment appears to be valid
- // let's make sure this player is allowed to drag onto this part
- // getting attached to (hearty)
- let target_part = {
- let Ok(target_part) = parts.get(target_joint.1.0) else {
+ // getting attached to (hearty)
+ let Ok((target_xform, target_in_ship, target_entity, target_linvel, _, target_angvel, _)) = parts.get(target_jt_of.0) else {
continue;
};
- target_part
- };
- // attached (housing)
- let source_part = {
- let Ok(source_part) = parts.get(source_joint.1.0) else {
+ // attached (housing)
+ let Ok((_, _, source_entity, _, source_joints, _, _)) = parts.get(source_jt_of.0) else {
continue;
};
- source_part
- };
- let allowed = target_joint.1.0 == player_hearty_entity
- || target_part.1.is_some_and(|u| u.0 == player_hearty_entity);
- if !allowed {
- warn!("drag request: this player cannot move this part, ignoring request");
- continue;
- }
-
- // great, we have a valid peering request
-
- let mut processed = vec![source_joint.4];
- disconnect_part(
- (source_part.2, source_part.4),
- &joints,
- &q_joints,
- &mut processed,
- commands.reborrow(),
- );
+ let allowed = target_jt_of.0 == player_hearty_entity
+ || target_in_ship.is_some_and(|u| u.0 == player_hearty_entity);
+ if !allowed {
+ warn!("drag request: this player cannot move this part, ignoring request");
+ continue;
+ }
+
+ let mut processed = vec![source_jt_id];
+ propagate_disconnect(
+ (source_entity, source_joints),
+ &joints,
+ &q_joints,
+ &mut processed,
+ commands.reborrow(),
+ );
- // create the joint...
- let joint = FixedJoint::new(target_part.2, source_part.2)
- .with_local_anchor1(target_joint.2.translation.xy().into())
- .with_local_basis1(target_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0 as f64 + PI
- - source_joint.0.transform.rotation.to_euler(EulerRot::ZYX).0 as f64)
- .with_point_compliance(world_config.part.joint_point_compliance)
- .with_angle_compliance(world_config.part.joint_angle_compliance);
- let joint_damping = ModuleJointDamping {
- distance: world_config.part.joint_distance_damping,
- angular: world_config.part.joint_angular_damping,
- };
-
- let joint_id = commands.spawn((joint, joint_damping)).id();
-
- // create the peering component...
- commands.entity(source_joint.4).insert(Peer {
- peer_joint_entity_id: target_joint.4,
- physics_joint: joint_id
- });
- commands.entity(target_joint.4).insert(Peer {
- peer_joint_entity_id: source_joint.4,
- physics_joint: joint_id
- });
-
- // propagate PartInShip...
-
- let part_in_ship = if target_joint.1.0 == player_hearty_entity {
- PartInShip(player_hearty_entity)
- } else {
- PartInShip(target_part.1.unwrap().0) // unwrap: checked above (during 'allowed' calculation)
- };
-
- commands.entity(source_part.2).insert(part_in_ship);
-
- let target_position = target_part.0.mul_transform(*target_joint.2);
-
- teleport_to_translation = target_position.translation.xy();
- teleport_to_rotation = target_position.rotation
- * source_joint.0.transform.rotation.inverse()
- * Quat::from_rotation_z(PI as f32);
- new_linvel = Some(*target_part.3);
- new_angvel = Some(*target_part.5);
- // and we're done!
- } else {
- warn!(
- "blindly accepting non-attachment request, someone should change this eventually"
- );
- warn!("dragging already attached entities may cause inconsistent behavior!!");
- let source_part = parts.get(event.drag_target).unwrap();
- let mut processed = vec![];
- disconnect_part(
- (source_part.2, source_part.4),
- &joints,
- &q_joints,
- &mut processed,
- commands.reborrow(),
- );
- teleport_to_translation = event.drag_to;
- teleport_to_rotation = event.set_rotation;
+ // create the physics joint
+ let fixed_joint = FixedJoint::new(target_entity, source_entity)
+ .with_local_anchor1(target_jt_xform.translation.xy().into())
+ .with_local_basis1(target_jt.transform.rotation.to_euler(EulerRot::ZYX).0 as f64 + PI
+ - source_jt.transform.rotation.to_euler(EulerRot::ZYX).0 as f64)
+ .with_point_compliance(world_config.part.joint_point_compliance)
+ .with_angle_compliance(world_config.part.joint_angle_compliance);
+ let joint_damping = ModuleJointDamping {
+ distance: world_config.part.joint_distance_damping,
+ angular: world_config.part.joint_angular_damping,
+ };
+ let joint_id = commands.spawn((fixed_joint, joint_damping)).id();
+
+ // create the peering components
+ commands.entity(source_jt_id).insert(Peer {
+ peer_joint_entity_id: target_jt_id,
+ physics_joint: joint_id,
+ });
+ commands.entity(target_jt_id).insert(Peer {
+ peer_joint_entity_id: source_jt_id,
+ physics_joint: joint_id,
+ });
+
+ // propagate PartInShip
+ let part_in_ship = if target_jt_of.0 == player_hearty_entity {
+ PartInShip(player_hearty_entity)
+ } else {
+ PartInShip(target_in_ship.unwrap().0) // unwrap: checked above (during 'allowed' calculation)
+ };
+ commands.entity(source_entity).insert(part_in_ship);
+
+ let target_position = target_xform.mul_transform(*target_jt_xform);
+ teleport_to_translation = target_position.translation.xy();
+ teleport_to_rotation = target_position.rotation
+ * source_jt.transform.rotation.inverse()
+ * Quat::from_rotation_z(PI as f32);
+ new_linvel = Some(*target_linvel);
+ new_angvel = Some(*target_angvel);
+ }
+ DragAction::Free { position, rotation } => {
+ warn!("blindly accepting non-attachment request, someone should change this eventually");
+ warn!("dragging already attached entities may cause inconsistent behavior!!");
+ let Ok((_, _, free_entity, _, free_joints, _, _)) = parts.get(event.drag_target) else {
+ continue;
+ };
+ let mut processed = vec![];
+ propagate_disconnect(
+ (free_entity, free_joints),
+ &joints,
+ &q_joints,
+ &mut processed,
+ commands.reborrow(),
+ );
+ teleport_to_translation = *position;
+ teleport_to_rotation = *rotation;
+ }
}
- let mut part = parts.get_mut(event.drag_target).unwrap();
- part.0.translation.x = teleport_to_translation.x;
- part.0.translation.y = teleport_to_translation.y;
- part.0.rotation = teleport_to_rotation; // client calculates this; no reason to recalculate
- if let Some(new_vel) = new_linvel {
- *part.3 = new_vel;
+ let Ok((mut xform, _, _, mut linvel, _, mut angvel, _)) = parts.get_mut(event.drag_target) else {
+ continue;
+ };
+ xform.translation.x = teleport_to_translation.x;
+ xform.translation.y = teleport_to_translation.y;
+ xform.rotation = teleport_to_rotation; // client calculates this; no reason to recalculate
+ if let Some(vel) = new_linvel {
+ *linvel = vel;
}
- if let Some(new_vel) = new_angvel {
- *part.5 = new_vel;
+ if let Some(vel) = new_angvel {
+ *angvel = vel;
}
- // ( the math sucks )
}
}
M crates/unified/src/shared/ecs.rs => crates/unified/src/shared/ecs.rs +7 -4
@@ 42,10 42,13 @@ pub struct Player {
#[derive(Message, Debug, Clone)]
pub struct DragRequestEvent {
pub drag_target: Entity,
- pub drag_to: Vec2,
- pub set_rotation: Quat,
- pub snap_target: Option<Entity>,
- pub peer_snap: Option<Entity>,
+ pub action: DragAction,
+}
+
+#[derive(Debug, Clone)]
+pub enum DragAction {
+ Attach { snap_target: Entity, peer_snap: Entity },
+ Free { position: Vec2, rotation: Quat },
}
#[derive(Component, Serialize, Deserialize, Debug)]