Skip to main content

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//! ![](https://i.imgur.com/UkqynLa.png)
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;