~starkingdoms/starkingdoms

9e70dffdd8c869d93630442f61801a7d4b56ee30 — core 5 months ago e49dc2f
feat: proper part rotation
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));
                    }
                }
            }
        }