cartesian_trajectories/lib.rs
1// Copyright (c) 2021 Marco Boneberger
2// Licensed under the EUPL-1.2-or-later
3#![deny(missing_docs)]
4//! This library generate smooth trajectories in cartesian space in a functional style.
5//!
6//! # Overview
7//! The idea is to specify a function that takes a start pose and a progress between 0 and 1 and spits out a pose.
8//! This function is the [`PoseGenerator`](`PoseGenerator`). It is possible to directly define the function
9//! or to combine a [`PositionGenerator`](`PositionGenerator`) and an [`OrientationGenerator`](`OrientationGenerator`).
10//! Further, there are predefined PoseGenerators available. See [`pose_generators`](`crate::pose_generators`).
11//!
12//! You should make sure that your own PoseGenerators have a constant derivative, that means that all if you would sample
13//! 1000 equidistant points, the length between neighboring points should be the same. Otherwise, some parts of
14//! your trajectory will end up faster than other ones.
15//!
16//! PoseGenerators can be multiplied to generate a new PoseGenerator. Multiplication means that the
17//! homogenous matrices of both pose generators will be multiplied together for a certain progress.
18//!
19//! Further, it is possible to combine PoseGenerators with the [`append`](`PoseGenerator::append`) method,
20//! which will concatenate both PoseGenerators. Be aware that concatenating PoseGenerators can result in unsmooth
21//! trajectories. For example, if you append a circle PoseGenerator to a linear PoseGenerator, there will be an infinite
22//! jerk at the transition as the acceleration suddenly changes.
23//!
24//! With the final PoseGenerator it is now possible to add a VelocityProfile to it, which turns
25//! the PoseGenerator into a [`CartesianTrajectory`](`crate::cartesian_trajectory::CartesianTrajectory`) which can be queried
26//! with the [`get_pose`](`crate::cartesian_trajectory::CartesianTrajectory::get_pose`) method
27//! with a start pose and a Duration.
28//!
29//! The following images shows how this library can be used to create a complex trajectory
30//! 
31//!
32use nalgebra::{Isometry3, UnitQuaternion, Vector3};
33
34/// Struct that wraps a function that generates a pose
35pub struct PoseGenerator(Box<dyn FnMut(&Isometry3<f64>, f64) -> Isometry3<f64>>);
36
37/// Struct that wraps a function that generates a position
38pub struct PositionGenerator(Box<dyn FnMut(&Vector3<f64>, f64) -> Vector3<f64>>);
39
40/// Struct that wraps a function that generates an orientation
41pub struct OrientationGenerator(Box<dyn FnMut(&UnitQuaternion<f64>, f64) -> UnitQuaternion<f64>>);
42
43impl PositionGenerator {
44 /// generates a new PositionGenerator by defining a function that takes
45 /// an initial position and a progress between 0 and 1 and returns a position.
46 pub fn new(
47 position_generator_function: Box<dyn FnMut(&Vector3<f64>, f64) -> Vector3<f64>>,
48 ) -> Self {
49 PositionGenerator(position_generator_function)
50 }
51 /// Evaluates the function of the PositionGenerator
52 /// # Arguments
53 /// * `start_position` - the position at the start time of the PositionGenerator
54 /// * `progress` - a progress from 0, indicating the start, and 1 , indicating the end of the
55 /// trajectory.
56 pub fn get_position(&mut self, start_position: &Vector3<f64>, progress: f64) -> Vector3<f64> {
57 (self.0)(start_position, progress)
58 }
59 /// creates a position generator that does not move and therefore always returns the start position.
60 pub fn constant_position_generator() -> Self {
61 let position_generator = move |start_position: &Vector3<f64>,
62 _progress: f64|
63 -> Vector3<f64> { start_position.clone_owned() };
64 PositionGenerator(Box::new(position_generator))
65 }
66}
67
68impl OrientationGenerator {
69 /// generates a new OrientationGenerator by defining a function that takes
70 /// an initial orientation and a progress between 0 and 1 and returns an orientation.
71 pub fn new(
72 orientation_generator_function: Box<
73 dyn FnMut(&UnitQuaternion<f64>, f64) -> UnitQuaternion<f64>,
74 >,
75 ) -> Self {
76 OrientationGenerator(orientation_generator_function)
77 }
78 /// Evaluates the function of the OrientationGenerator
79 /// # Arguments
80 /// * `start_orientation` - the orientation at the start time of the OrientationGenerator
81 /// * `progress` - a progress from 0, indicating the start, and 1 , indicating the end of the
82 /// trajectory.
83 pub fn get_orientation(
84 &mut self,
85 start_orientation: &UnitQuaternion<f64>,
86 progress: f64,
87 ) -> UnitQuaternion<f64> {
88 (self.0)(start_orientation, progress)
89 }
90 /// creates an orientation generator that does not rotate and therefore always returns the start orientation.
91 pub fn constant_orientation_generator() -> Self {
92 let orientation_generator = move |start_orientation: &UnitQuaternion<f64>,
93 _progress: f64|
94 -> UnitQuaternion<f64> { *start_orientation };
95 OrientationGenerator(Box::new(orientation_generator))
96 }
97}
98
99impl PoseGenerator {
100 /// generates a new PoseGenerator by defining a function that takes
101 /// an initial position and a progress between 0 and 1 and returns a position.
102 pub fn new(
103 pose_generator_function: Box<dyn FnMut(&Isometry3<f64>, f64) -> Isometry3<f64>>,
104 ) -> Self {
105 PoseGenerator(pose_generator_function)
106 }
107 /// Generates a PoseGenerator from a position and an orientation generator.
108 pub fn from_parts(
109 mut position_generator: PositionGenerator,
110 mut orientation_generator: OrientationGenerator,
111 ) -> Self {
112 let pose_generator =
113 move |initial_pose: &Isometry3<f64>, progress: f64| -> Isometry3<f64> {
114 let position =
115 position_generator.get_position(&initial_pose.translation.vector, progress);
116 let orientation =
117 orientation_generator.get_orientation(&initial_pose.rotation, progress);
118 Isometry3::from_parts(position.into(), orientation)
119 };
120 PoseGenerator(Box::new(pose_generator))
121 }
122 /// Evaluates the function of the PoseGenerator
123 /// # Arguments
124 /// * `start_pose` - the pose at the start time of the PoseGenerator
125 /// * `progress` - a progress from 0, indicating the start, and 1 , indicating the end of the
126 /// trajectory.
127 pub fn get_pose(&mut self, start: &Isometry3<f64>, progress: f64) -> Isometry3<f64> {
128 (self.0)(start, progress)
129 }
130 /// returns the approximate length of the trajectory in meter. The rotation is ignored.
131 pub fn get_approximate_length(
132 &mut self,
133 start_pose: &Isometry3<f64>,
134 sample_size: usize,
135 ) -> f64 {
136 (0..sample_size)
137 .map(|x| {
138 (
139 x as f64 / sample_size as f64,
140 (x as f64 + 1.) / sample_size as f64,
141 )
142 })
143 .map(|(t1, t2)| {
144 (self.get_pose(&start_pose, t1).translation.inverse()
145 * self.get_pose(&start_pose, t2).translation)
146 .vector
147 .norm()
148 })
149 .sum()
150 }
151 /// concatenates two pose generators.
152 /// Be aware that concatenating PoseGenerators can result in unsmooth
153 /// trajectories. For example, if you append a circle PoseGenerator to a linear PoseGenerator,
154 /// there will be an infinite jerk at the transition as the acceleration suddenly changes.
155 /// # Arguments
156 /// * `start_pose` - a rough estimation of the start pose of the trajectory.
157 /// * `other_generator` - the pose generator that should be appended
158 pub fn append(
159 mut self,
160 start_pose: &Isometry3<f64>,
161 mut other_generator: PoseGenerator,
162 ) -> PoseGenerator {
163 let length_self = self.get_approximate_length(start_pose, 10000);
164 let end_pose_self = self.get_pose(start_pose, 1.);
165 let length_other = other_generator.get_approximate_length(&end_pose_self, 10000);
166 let length_split = length_self / (length_self + length_other);
167
168 let pose_generator =
169 move |initial_pose: &Isometry3<f64>, progress: f64| -> Isometry3<f64> {
170 if progress < length_split {
171 self.get_pose(initial_pose, progress / length_split)
172 } else {
173 other_generator.get_pose(
174 &end_pose_self,
175 (progress - length_split) / (1. - length_split),
176 )
177 }
178 };
179 PoseGenerator(Box::new(pose_generator))
180 }
181}
182
183impl std::ops::Mul for PoseGenerator {
184 type Output = PoseGenerator;
185
186 fn mul(mut self, mut rhs: Self) -> Self::Output {
187 let pose_generator =
188 move |initial_pose: &Isometry3<f64>, progress: f64| -> Isometry3<f64> {
189 (self.0)(initial_pose, progress) * (rhs.0)(initial_pose, progress)
190 };
191 PoseGenerator(Box::new(pose_generator))
192 }
193}
194mod cartesian_trajectory;
195pub mod pose_generators;
196mod velocity_profile;
197pub use crate::cartesian_trajectory::{
198 CartesianTrajectory, CartesianTrajectoryOutput, VelocityProfile,
199};
200pub use crate::pose_generators::{
201 generate_absolute_motion, generate_circle_motion, generate_relative_motion, RelativeMotionFrame,
202};
203pub use crate::velocity_profile::{
204 generate_cosine_velocity_profile, generate_linear_velocity_profile, generate_s_curve_profile,
205 VelocityProfileMapping, VelocityProfileOutput,
206};
207pub use s_curve;