M crates/unified/assets/config/parts/chassis.part.toml => crates/unified/assets/config/parts/chassis.part.toml +7 -7
@@ 10,21 10,21 @@ mass = 100
[[joints]]
id = "Top"
-target = { translation = [ 0.0, 50.0, 0.0 ], rotation = 0.0 }
+target = { translation = [ 0.0, 50.0, 0.0 ], rotation = 180.0 }
snap = { translation = [ 0.0, 25.0, 0.0 ], rotation = 0.0 }
[[joints]]
id = "Right"
-target = { translation = [ 50.0, 0.0, 0.0 ], rotation = 90.0 }
-snap = { translation = [ 25.0, 0.0, 0.0 ], rotation = 90.0 }
+target = { translation = [ 50.0, 0.0, 0.0 ], rotation = 270.0 }
+snap = { translation = [ 25.0, 0.0, 0.0 ], rotation = 0.0 }
[[joints]]
id = "Bottom"
-target = { translation = [ 0.0, -50.0, 0.0 ], rotation = 180.0 }
-snap = { translation = [ 0.0, -25.0, 0.0 ], rotation = 180.0 }
+target = { translation = [ 0.0, -50.0, 0.0 ], rotation = 0.0 }
+snap = { translation = [ 0.0, -25.0, 0.0 ], rotation = 0.0 }
[[joints]]
id = "Left"
-target = { translation = [ -50.0, 0.0, 0.0 ], rotation = 270.0 }
-snap = { translation = [ -25.0, 0.0, 0.0 ], rotation = 270.0 }>
\ No newline at end of file
+target = { translation = [ -50.0, 0.0, 0.0 ], rotation = 90.0 }
+snap = { translation = [ -25.0, 0.0, 0.0 ], rotation = 0.0 }<
\ No newline at end of file
M crates/unified/assets/config/parts/hearty.part.toml => crates/unified/assets/config/parts/hearty.part.toml +7 -7
@@ 14,21 14,21 @@ exhaust_speed = 250
[[joints]]
id = "Top"
-target = { translation = [ 0.0, 50.0, 0.0 ], rotation = 0.0 }
+target = { translation = [ 0.0, 50.0, 0.0 ], rotation = 180.0 }
snap = { translation = [ 0.0, 25.0, 0.0 ], rotation = 0.0 }
[[joints]]
id = "Right"
-target = { translation = [ 50.0, 0.0, 0.0 ], rotation = 90.0 }
-snap = { translation = [ 25.0, 0.0, 0.0 ], rotation = 90.0 }
+target = { translation = [ 50.0, 0.0, 0.0 ], rotation = 270.0 }
+snap = { translation = [ 25.0, 0.0, 0.0 ], rotation = 0.0 }
[[joints]]
id = "Bottom"
-target = { translation = [ 0.0, -50.0, 0.0 ], rotation = 180.0 }
-snap = { translation = [ 0.0, -25.0, 0.0 ], rotation = 180.0 }
+target = { translation = [ 0.0, -50.0, 0.0 ], rotation = 0.0 }
+snap = { translation = [ 0.0, -25.0, 0.0 ], rotation = 0.0 }
[[joints]]
id = "Left"
-target = { translation = [ -50.0, 0.0, 0.0 ], rotation = 270.0 }
-snap = { translation = [ -25.0, 0.0, 0.0 ], rotation = 270.0 }
+target = { translation = [ -50.0, 0.0, 0.0 ], rotation = 90.0 }
+snap = { translation = [ -25.0, 0.0, 0.0 ], rotation = 0.0 }<
\ No newline at end of file
M crates/unified/src/attachment.rs => crates/unified/src/attachment.rs +10 -4
@@ 22,7 22,7 @@ pub struct Joint {
pub transform: Transform,
}
#[derive(Component, Serialize, Deserialize, MapEntities)]
-pub struct Peer(#[entities] Entity);
+pub struct Peer(#[entities] pub Entity);
#[derive(Component, Serialize, Deserialize, MapEntities)]
#[relationship(relationship_target = Joints)]
@@ 30,6 30,12 @@ pub struct JointOf(#[entities] pub Entity);
#[derive(Component, Serialize, Deserialize, MapEntities)]
#[relationship_target(relationship = JointOf)]
pub struct Joints(#[entities] Vec<Entity>);
+impl Deref for Joints {
+ type Target = Vec<Entity>;
+ fn deref(&self) -> &Self::Target {
+ &self.0
+ }
+}
#[derive(Component, Serialize, Deserialize, MapEntities)]
#[relationship(relationship_target = Snaps)]
pub struct SnapOf(#[entities] pub Entity);
@@ 44,12 50,12 @@ impl Deref for Snaps {
}
}
-#[derive(Serialize, Deserialize)]
+#[derive(Serialize, Deserialize, PartialEq, Eq, Clone)]
pub struct JointId(pub String);
impl JointId {
#[must_use]
- pub fn from_part_and_joint_id(part: String, joint: String) -> Self {
- Self(format!("{part}:{joint}"))
+ pub fn from_part_and_joint_id(part: impl AsRef<str>, joint: impl AsRef<str>) -> Self {
+ Self(format!("{}:{}", part.as_ref(), joint.as_ref()))
}
}
M crates/unified/src/client/parts.rs => crates/unified/src/client/parts.rs +49 -9
@@ 3,7 3,7 @@ use crate::client::Me;
use crate::client::colors::GREEN;
use crate::client::key_input::AttachmentDebugRes;
use crate::ecs::{CursorWorldCoordinates, DragRequestEvent, Part};
-use bevy::color::palettes::css::{ORANGE, RED};
+use bevy::color::palettes::css::{ORANGE, PURPLE, RED, YELLOW};
use bevy::prelude::*;
use bevy_rapier2d::dynamics::MassProperties;
use bevy_rapier2d::prelude::AdditionalMassProperties;
@@ 173,6 173,11 @@ fn update_drag_ghosts(
let mut best_target = Transform::from_xyz(cursor.x, cursor.y, 0.0);
best_target.rotation = ghost_target.0;
let mut snap = None;
+ let mut best_parent_position = None;
+ let mut best_snap_position = None;
+ let mut best_self_snap = None;
+ let mut best_self_snap_position = None;
+ 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 {
@@ 191,26 196,25 @@ fn update_drag_ghosts(
}
let snap_global_translation = parent_position
- .transform_point(snap_local_transform.translation)
- .xy();
+ .mul_transform(*snap_local_transform);
- let distance_to_cursor = cursor.distance(snap_global_translation);
+ let distance_to_cursor = cursor.distance(snap_global_translation.translation().xy());
if distance_to_cursor > best_distance {
if debug.0 {
- gizmos.circle_2d(snap_global_translation, 3.0, RED);
+ gizmos.circle_2d(snap_global_translation.translation().xy(), 3.0, RED);
}
continue;
}
if distance_to_cursor > CUTOFF {
if debug.0 {
- gizmos.circle_2d(snap_global_translation, 3.0, ORANGE);
+ gizmos.circle_2d(snap_global_translation.translation().xy(), 3.0, ORANGE);
}
continue;
}
if debug.0 {
- gizmos.circle_2d(snap_global_translation, 3.0, GREEN);
+ gizmos.circle_2d(snap_global_translation.translation().xy(), 3.0, GREEN);
}
let Ok((offset, _, _)) = joints.get(snap_joint.0) else {
@@ 223,17 227,53 @@ fn update_drag_ghosts(
gizmos.circle_2d(joint_target.xy(), 3.0, GREEN);
}
+ snap = Some(snap_id);
let target_transform = Transform {
translation: joint_target,
- rotation: ghost_target.0,
+ rotation: ghost.rotation,
scale: offset.scale,
};
best_distance = distance_to_cursor;
best_target = target_transform;
+ best_parent_position = Some(parent_position.translation().xy());
+ best_snap_position = Some(snap_global_translation);
+
+ // find, in this particular situation, the best peer on ourselves
+ let mut best_peer_snap_distance = f32::INFINITY;
+ let mut best_peer_snap = None;
+ let mut best_peer_snap_pos = None;
+ 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 { continue; }
+ let our_snap_global_translation = best_target
+ .mul_transform(*our_snap_local_transform);
+ let distance = our_snap_global_translation.translation.distance(snap_global_translation.translation());
+ if distance > best_peer_snap_distance { continue; }
+
+ let Ok((our_joint, _, _)) = joints.get(our_snap_joint.0) else { continue };
+
+ best_peer_snap = Some(our_snap_id);
+ best_peer_snap_distance = distance;
+ best_peer_snap_pos = Some(our_snap_global_translation.translation.xy());
+ best_peer_target_pos = Some(best_target.translation.xy());
+ best_joint_transform = Some(our_joint);
+ }
+ if let Some(our_joint) = best_joint_transform {
+ best_target.rotation = parent_position.rotation().mul_quat(our_joint.rotation);
+ }
- snap = Some(snap_id);
+ best_self_snap = best_peer_snap;
+ best_self_snap_position = best_peer_snap_pos;
+ best_self_position = best_peer_target_pos;
}
+ if debug.0 && let Some(part) = best_parent_position && let Some(snap) = best_snap_position {
+ gizmos.arrow_2d(part, snap.translation().xy(), YELLOW);
+ }
+ if debug.0 && let Some(part) = best_self_position && let Some(snap) = best_self_snap_position {
+ gizmos.arrow_2d(part, snap, PURPLE);
+ }
if keys.pressed(KeyCode::KeyQ) {
best_target.rotation = best_target.rotation.mul_quat(Quat::from_rotation_z(
M crates/unified/src/server/part.rs => crates/unified/src/server/part.rs +46 -2
@@ 1,4 1,5 @@
-use crate::attachment::{Joint, JointId, JointOf, SnapOf, SnapOfJoint};
+use std::cmp::PartialEq;
+use crate::attachment::{Joint, JointId, JointOf, Joints, Peer, SnapOf, SnapOfJoint};
use crate::config::part::{JointConfig, PartConfig};
use crate::ecs::{Part, PartHandle};
use bevy::prelude::Component;
@@ 36,8 37,11 @@ fn handle_ready_parts(
}
}
}
+
fn handle_part_reloading(
- existing_parts: Query<(Entity, &PartHandle)>,
+ existing_parts: Query<(Entity, &PartHandle, &Joints)>,
+ joints: Query<(&mut Joint, Option<&Peer>, Entity)>,
+ snaps: Query<(Entity, &SnapOfJoint)>,
assets: Res<Assets<PartConfig>>,
mut asset_events: EventReader<AssetEvent<PartConfig>>,
mut commands: Commands,
@@ 50,6 54,46 @@ fn handle_part_reloading(
commands
.entity(existing_part.0)
.insert(calculate_bundle(config, &existing_part.1.0));
+ // update all joints
+ let mut used_joints = vec![];
+ for joint_id in &**existing_part.2 {
+ // find in config
+ let Ok((joint, peer, _)) = joints.get(*joint_id) else { continue };
+ let joint_cfg = config.joints.iter().find(|u| joint.id == JointId::from_part_and_joint_id(&config.part.name, &u.id));
+ let Some(joint_cfg) = joint_cfg else {
+ if let Some(peer_id) = peer {
+ if let Ok(peer) = joints.get(peer_id.0) {
+ commands.entity(peer.2).remove::<Peer>();
+ }
+ }
+ commands.entity(*joint_id).despawn();
+ for snap in &snaps {
+ if snap.1.0 == *joint_id {
+ commands.entity(snap.0).despawn();
+ }
+ }
+ continue;
+ };
+ used_joints.push(joint.id.clone());
+ commands
+ .entity(*joint_id)
+ .insert(spawn_joint_bundle(joint_cfg, config, &existing_part.0));
+ // annihilate all snaps then respawn
+ for snap in &snaps {
+ if snap.1.0 == *joint_id {
+ commands.entity(snap.0).despawn();
+ }
+ }
+ commands.spawn(spawn_snap_bundle(joint_cfg, &existing_part.0, joint_id));
+ }
+ for joint in &config.joints {
+ let id = JointId::from_part_and_joint_id(&config.part.name, &joint.id);
+ if used_joints.contains(&id) { continue };
+ let joint_id = commands
+ .spawn(spawn_joint_bundle(joint, config, &existing_part.0))
+ .id();
+ commands.spawn(spawn_snap_bundle(joint, &existing_part.0, &joint_id));
+ }
}
}
}