use crate::{PoseGenerator, PositionGenerator};
use nalgebra::{Isometry3, Unit, Vector3};
pub enum RelativeMotionFrame {
Base,
Tcp,
Unit,
}
pub fn generate_absolute_motion(goal_pose: Isometry3<f64>) -> PoseGenerator {
let pose_generator = move |initial_pose: &Isometry3<f64>, progress: f64| -> Isometry3<f64> {
initial_pose.lerp_slerp(&goal_pose, progress)
};
PoseGenerator(Box::new(pose_generator))
}
pub fn generate_relative_motion(
displacement: Isometry3<f64>,
frame: RelativeMotionFrame,
) -> PoseGenerator {
match frame {
RelativeMotionFrame::Base => {
let pose_generator =
move |initial_pose: &Isometry3<f64>, progress: f64| -> Isometry3<f64> {
let goal = displacement * initial_pose;
initial_pose.lerp_slerp(&goal, progress)
};
PoseGenerator(Box::new(pose_generator))
}
RelativeMotionFrame::Tcp => {
let pose_generator =
move |initial_pose: &Isometry3<f64>, progress: f64| -> Isometry3<f64> {
let goal = initial_pose * displacement;
initial_pose.lerp_slerp(&goal, progress)
};
PoseGenerator(Box::new(pose_generator))
}
RelativeMotionFrame::Unit => {
let pose_generator =
move |_initial_pose: &Isometry3<f64>, progress: f64| -> Isometry3<f64> {
Isometry3::identity().lerp_slerp(&displacement, progress)
};
PoseGenerator(Box::new(pose_generator))
}
}
}
fn angle_between(a: &Vector3<f64>, b: &Vector3<f64>, normal: &Unit<Vector3<f64>>) -> f64 {
f64::atan2(b.cross(a).dot(normal), a.dot(b))
}
pub fn generate_circle_motion(
center: Vector3<f64>,
normal: Unit<Vector3<f64>>,
start: Vector3<f64>,
rotation_angle: f64,
) -> PositionGenerator {
let center_direction = center - start;
let mut radius: f64 = center_direction.norm();
assert!(
radius > 1e-7,
"start and center are too close to each other"
);
let mut v1 = normal.cross(¢er_direction).normalize();
let mut v2 = normal.cross(&v1);
let tmp_start = radius * v1;
let mut start_angle = angle_between(&-center_direction, &tmp_start, &normal);
let position_generator = move |start_position: &Vector3<f64>, progress: f64| -> Vector3<f64> {
if progress == 0. {
let center_direction = center - start_position;
radius = center_direction.norm();
assert!(
radius > 1e-7,
"start and center are too close to each other"
);
v1 = normal.cross(¢er_direction).normalize();
v2 = normal.cross(&v1);
let tmp_start = radius * v1;
start_angle = angle_between(&-center_direction, &tmp_start, &normal);
}
let desired_angle = start_angle + progress * rotation_angle;
center + radius * (v1 * f64::cos(desired_angle) + v2 * f64::sin(desired_angle))
};
PositionGenerator(Box::new(position_generator))
}
#[cfg(test)]
mod tests {
use crate::pose_generators::*;
use crate::OrientationGenerator;
use nalgebra::{Isometry3, Translation3, UnitQuaternion, Vector3};
use std::f64::consts::PI;
#[test]
fn can_translate() {
let start = Isometry3::translation(0., 0., 0.);
let goal = Isometry3::translation(1., 1., 1.);
let mut pose_generator = generate_absolute_motion(goal);
for i in 0..=100 {
let progress = i as f64 / 100.;
let pose: Isometry3<f64> = pose_generator.get_pose(&start, progress);
assert!(f64::abs(pose.translation.x - progress) < 1e-10);
assert!(f64::abs(pose.translation.y - progress) < 1e-10);
assert!(f64::abs(pose.translation.z - progress) < 1e-10);
}
}
#[test]
fn can_calculate_length() {
let start = Isometry3::translation(2., 2., 0.);
let goal = Isometry3::translation(1., 1., 0.);
let mut pose_generator = generate_absolute_motion(goal);
let length = pose_generator.get_approximate_length(&start, 10000);
assert!(f64::abs(length - std::f64::consts::SQRT_2) < 1e-7);
}
#[test]
fn can_rotate() {
let start_roll = 0.1;
let start_pitch = 0.2;
let start_yaw = 0.3;
let goal_roll = 0.2;
let goal_pitch = 0.4;
let goal_yaw = 0.4;
let start = Isometry3::from_parts(
Translation3::new(0., 0., 0.),
UnitQuaternion::from_euler_angles(start_roll, start_pitch, start_yaw),
);
let goal = Isometry3::from_parts(
Translation3::new(0., 0., 0.),
UnitQuaternion::from_euler_angles(goal_roll, goal_pitch, goal_yaw),
);
let mut pose_generator = generate_absolute_motion(goal);
let start_pose: Isometry3<f64> = pose_generator.get_pose(&start, 0.);
let (roll, pitch, yaw) = start_pose.rotation.euler_angles();
assert!(f64::abs(roll - start_roll) < 1e-10);
assert!(f64::abs(pitch - start_pitch) < 1e-10);
assert!(f64::abs(yaw - start_yaw) < 1e-10);
let goal_pose: Isometry3<f64> = pose_generator.get_pose(&start, 1.);
let (roll, pitch, yaw) = goal_pose.rotation.euler_angles();
assert!(f64::abs(roll - goal_roll) < 1e-10);
assert!(f64::abs(pitch - goal_pitch) < 1e-10);
assert!(f64::abs(yaw - goal_yaw) < 1e-10);
}
#[test]
fn can_do_relative_translation() {
let start = Isometry3::translation(1., 1., 0.);
let goal = Isometry3::translation(1., 0., 0.);
let mut pose_generator = generate_relative_motion(goal, RelativeMotionFrame::Base);
for i in 0..=100 {
let progress = i as f64 / 100.;
let pose: Isometry3<f64> = pose_generator.get_pose(&start, progress);
println!("{:?}", pose.translation);
assert!(f64::abs(pose.translation.x - progress - 1.) < 1e-10);
assert!(f64::abs(pose.translation.y - 1.) < 1e-10);
assert!(f64::abs(pose.translation.z) < 1e-10);
}
}
#[test]
fn can_do_relative_translation_in_tcp_frame() {
let start = Isometry3::rotation(Vector3::new(0., 0., std::f64::consts::FRAC_PI_2));
let goal = Isometry3::translation(1., 0., 0.);
let mut pose_generator = generate_relative_motion(goal, RelativeMotionFrame::Tcp);
for i in 0..=100 {
let progress = i as f64 / 100.;
let pose: Isometry3<f64> = pose_generator.get_pose(&start, progress);
println!("{:?}", pose.translation);
assert!(f64::abs(pose.translation.x) < 1e-10);
assert!(f64::abs(pose.translation.y - progress) < 1e-10);
assert!(f64::abs(pose.translation.z) < 1e-10);
}
}
#[test]
fn can_multiply() {
let start = Isometry3::translation(1., -1., 0.5);
let goal_1 = Isometry3::translation(1., 0., 0.);
let goal_2 = Isometry3::translation(0., 1., 0.);
let pose_generator_1 = generate_relative_motion(goal_1, RelativeMotionFrame::Unit);
let pose_generator_2 = generate_relative_motion(goal_2, RelativeMotionFrame::Base);
let mut pose_generator = pose_generator_1 * pose_generator_2;
for i in 0..=100 {
let progress = i as f64 / 100.;
let pose: Isometry3<f64> = pose_generator.get_pose(&start, progress);
println!("{:?}", pose.translation);
assert!(f64::abs(pose.translation.x - progress - 1.) < 1e-10);
assert!(f64::abs(pose.translation.y - progress + 1.) < 1e-10);
assert!(f64::abs(pose.translation.z - 0.5) < 1e-10);
}
}
#[test]
fn can_combine_equal_generators() {
let start = Isometry3::translation(1., 0., 0.5);
let goal_1 = Isometry3::translation(0., 1., 0.);
let goal_2 = Isometry3::translation(0., 1., 0.);
let pose_generator_1 = generate_relative_motion(goal_1, RelativeMotionFrame::Base);
let pose_generator_2 = generate_relative_motion(goal_2, RelativeMotionFrame::Base);
let mut pose_generator: PoseGenerator = pose_generator_1.append(&start, pose_generator_2);
let start_time = std::time::Instant::now();
for i in 0..=100 {
let progress = i as f64 / 100.;
let pose: Isometry3<f64> = pose_generator.get_pose(&start, progress);
println!("{:?}", pose.translation);
assert!(f64::abs(pose.translation.x - 1.) < 1e-10);
assert!(f64::abs(pose.translation.y - 2. * progress) < 1e-10);
assert!(f64::abs(pose.translation.z - 0.5) < 1e-10);
}
println!("{:?}", std::time::Instant::now() - start_time);
}
#[test]
fn can_combine_non_equal_generators() {
let start = Isometry3::translation(1., 0., 0.5);
let goal_1 = Isometry3::translation(0., 1., 0.);
let goal_2 = Isometry3::translation(2., 0., 0.);
let pose_generator_1 = generate_relative_motion(goal_1, RelativeMotionFrame::Base);
let pose_generator_2 = generate_relative_motion(goal_2, RelativeMotionFrame::Base);
let mut pose_generator: PoseGenerator = pose_generator_1.append(&start, pose_generator_2);
let split = 1. / 3.;
for i in 0..=100 {
let progress = i as f64 / 100.;
let pose: Isometry3<f64> = pose_generator.get_pose(&start, progress);
println!("{:?}", pose.translation);
if progress <= split {
assert!(f64::abs(pose.translation.x - 1.) < 1e-10);
assert!(f64::abs(pose.translation.y - 3. * progress) < 1e-10);
} else {
assert!(
f64::abs(pose.translation.x - 1. - 1. * 1. / split * (progress - split))
< 1e-10
);
assert!(f64::abs(pose.translation.y - 1.) < 1e-10);
}
assert!(f64::abs(pose.translation.z - 0.5) < 1e-10);
}
}
#[test]
fn can_do_circle() {
let start = Isometry3::translation(1., 0., 0.);
let circle_generator = generate_circle_motion(
Vector3::zeros(),
Vector3::z_axis(),
start.translation.vector,
PI,
);
let mut pose_generator = PoseGenerator::from_parts(
circle_generator,
OrientationGenerator::constant_orientation_generator(),
);
for i in 0..=100 {
let progress = i as f64 / 100.;
let pose: Isometry3<f64> = pose_generator.get_pose(&start, progress);
println!("{:?}", pose.translation);
assert!(f64::abs(pose.translation.x - f64::cos(PI * progress)) < 1e-10);
assert!(f64::abs(pose.translation.y - f64::sin(PI * progress)) < 1e-10);
assert!(f64::abs(pose.translation.z) < 1e-10);
}
}
}