@@ 1,3 1,5 @@
+use std::f32::consts::PI;
+
use crate::attachment::{Joint, JointOf, Joints, PartInShip, Peer, SnapOf, SnapOfJoint};
use crate::client::Me;
use crate::client::colors::GREEN;
@@ 115,9 117,11 @@ struct SnapResource(Option<Entity>, Option<Entity>);
#[derive(Component)]
struct DragGhost;
#[derive(Component)]
-struct GhostTarget(pub Quat);
-#[derive(Component)]
-struct GhostVelocity(pub Vec3);
+struct Ghost {
+ pub rot: Quat,
+ pub last_target_pos: Vec3,
+ pub vel: Vec3,
+}
const TRANSLATION_SMOOTH: f32 = 0.3;
const ROTATION_SMOOTH: f32 = 0.1;
@@ 147,7 151,11 @@ fn on_part_click(
}
let mut s = sprite.0.clone();
s.color = Color::srgba(0.7, 0.7, 0.7, 1.0);
- commands.spawn((DragGhost, GhostVelocity(Vec3::ZERO), GhostTarget(sprite.1.rotation), *sprite.1, s));
+ commands.spawn((DragGhost, Ghost {
+ rot: sprite.1.rotation,
+ last_target_pos: sprite.1.translation,
+ vel: Vec3::ZERO,
+ }, *sprite.1, s));
drag.0 = Some(ev.event().event_target());
}
@@ 191,7 199,7 @@ fn on_part_release(
/// This code is super cursed, and it will break at the lightest breeze
fn update_drag_ghosts(
ghost: Single<
- (&mut Transform, &mut GhostVelocity, &mut GhostTarget),
+ (&mut Transform, &mut Ghost),
(
With<DragGhost>,
Without<SnapOf>,
@@ 213,12 221,12 @@ fn update_drag_ghosts(
) {
const CUTOFF: f32 = 25.0; // px
- let (mut ghost, mut ghost_velocity, mut ghost_target) = ghost.into_inner();
+ let (mut ghost, mut ghost_info) = ghost.into_inner();
let Some(cursor) = cursor.0 else { return };
let mut best_distance = f32::INFINITY;
let mut best_target = Transform::from_xyz(cursor.x, cursor.y, 0.0);
- best_target.rotation = ghost_target.0;
+ best_target.rotation = ghost_info.rot;
let mut snap = None;
let mut best_parent_position = None;
let mut best_snap_position = None;
@@ 345,14 353,24 @@ fn update_drag_ghosts(
-180.0_f32.to_radians() * time.delta_secs(),
));
}
- ghost_target.0 = best_target.rotation;
- ghost.rotation = ghost.rotation.slerp(ghost_target.0, ROTATION_SMOOTH);
- let disp = ghost.translation - best_target.translation;
- const K: f32 = 0.07;
- const C: f32 = 0.5;
- let a = (-K*disp - C*ghost_velocity.0)/2.0;
- ghost.translation += ghost_velocity.0;
- ghost_velocity.0 += a;
+ 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();
+ const F: f32 = 2.0; // frequency (Hz)
+ // 0.0 is undamped, 0.0-1.0 is underdamped, 1.0 is critical, and >1.0 is over
+ const ZETA: f32 = 0.7; // damping
+ const R: f32 = -0.6; // initial response speed
+ const K1: f32 = ZETA/(PI*F);
+ const K2: f32 = 1.0/((2.0*PI*F)*(2.0*PI*F));
+ const K3: f32 = (R*ZETA)/(2.0*PI*F);
+ let a = (best_target.translation - ghost.translation
+ + K3*target_vel - K1*ghost_info.vel)/K2;
+
+ ghost.translation += ghost_info.vel*time.delta_secs();
+ ghost_info.vel += a*time.delta_secs();
+
+ ghost_info.last_target_pos = best_target.translation;
rsnap.0 = snap;
rsnap.1 = best_self_snap;