use crate::FrameIdString;
use cu_spatial_payloads::Transform3D;
use cu29::clock::{CuTime, CuTimeRange};
use dashmap::DashMap;
use num_traits;
use serde::{Deserialize, Serialize};
use std::collections::VecDeque;
use std::fmt::Debug;
use std::sync::{Arc, RwLock};
const DEFAULT_CACHE_SIZE: usize = 100;
fn ordered_by_stamp<T: Copy + Debug + Default + 'static>(
first: StampedTransform<T>,
second: StampedTransform<T>,
) -> (StampedTransform<T>, StampedTransform<T>) {
if first.stamp <= second.stamp {
(first, second)
} else {
(second, first)
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct StampedTransform<T: Copy + Debug + Default + 'static> {
pub transform: Transform3D<T>,
pub stamp: CuTime,
pub parent_frame: FrameIdString,
pub child_frame: FrameIdString,
}
impl<
T: Copy
+ Debug
+ 'static
+ Default
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ num_traits::NumCast,
> StampedTransform<T>
{
pub fn compute_velocity(
&self,
previous: &Self,
) -> Option<crate::velocity::VelocityTransform<T>> {
if self.parent_frame != previous.parent_frame || self.child_frame != previous.child_frame {
return None;
}
let dt_nanos = self.stamp.as_nanos() as i64 - previous.stamp.as_nanos() as i64;
if dt_nanos <= 0 {
return None;
}
let dt = dt_nanos as f64 / 1_000_000_000.0;
let dt_t = num_traits::cast::cast::<f64, T>(dt)?;
let self_mat = self.transform.to_matrix();
let prev_mat = previous.transform.to_matrix();
let mut linear_velocity = [T::default(); 3];
for (i, vel) in linear_velocity.iter_mut().enumerate() {
let pos_diff = self_mat[3][i] - prev_mat[3][i];
*vel = pos_diff / dt_t;
}
let rot1 = [
[prev_mat[0][0], prev_mat[0][1], prev_mat[0][2]],
[prev_mat[1][0], prev_mat[1][1], prev_mat[1][2]],
[prev_mat[2][0], prev_mat[2][1], prev_mat[2][2]],
];
let rot2 = [
[self_mat[0][0], self_mat[0][1], self_mat[0][2]],
[self_mat[1][0], self_mat[1][1], self_mat[1][2]],
[self_mat[2][0], self_mat[2][1], self_mat[2][2]],
];
let rot1_t = [
[rot1[0][0], rot1[1][0], rot1[2][0]],
[rot1[0][1], rot1[1][1], rot1[2][1]],
[rot1[0][2], rot1[1][2], rot1[2][2]],
];
let mut rot_diff = [[T::default(); 3]; 3];
for i in 0..3 {
for j in 0..3 {
let mut sum = T::default();
for (k, r1t) in rot1_t.iter().enumerate() {
sum = sum + (rot2[i][k] * r1t[j]);
}
rot_diff[i][j] = sum;
}
}
let mut angular_velocity = [T::default(); 3];
angular_velocity[0] = (rot_diff[2][1] - rot_diff[1][2]) / (dt_t + dt_t);
angular_velocity[1] = (rot_diff[0][2] - rot_diff[2][0]) / (dt_t + dt_t);
angular_velocity[2] = (rot_diff[1][0] - rot_diff[0][1]) / (dt_t + dt_t);
Some(crate::velocity::VelocityTransform {
linear: linear_velocity,
angular: angular_velocity,
})
}
}
#[derive(Clone, Debug, Serialize, Deserialize)]
struct TransformBufferInternal<T: Copy + Debug + Default + 'static> {
transforms: VecDeque<StampedTransform<T>>,
max_capacity: usize,
}
#[derive(Clone, Debug)]
pub struct ConstTransformBuffer<T: Copy + Debug + Default + 'static, const N: usize> {
transforms: [Option<StampedTransform<T>>; N],
count: usize,
head: usize, }
#[derive(Clone)]
pub struct TransformBuffer<T: Copy + Debug + Default + 'static> {
buffer: Arc<RwLock<TransformBufferInternal<T>>>,
}
#[derive(Clone)]
pub struct ConstTransformBufferSync<T: Copy + Debug + Default + 'static, const N: usize> {
buffer: Arc<RwLock<ConstTransformBuffer<T, N>>>,
}
impl<T: Copy + Debug + Default + 'static, const N: usize> ConstTransformBuffer<T, N> {
pub fn new() -> Self {
Self {
transforms: [const { None }; N],
count: 0,
head: 0,
}
}
pub fn add_transform(&mut self, transform: StampedTransform<T>) {
if self.count == 0 {
self.transforms[0] = Some(transform);
self.count = 1;
self.head = 1;
} else if self.count < N {
let mut insert_pos = 0;
for i in 0..self.count {
if let Some(ref t) = self.transforms[i] {
if t.stamp <= transform.stamp {
insert_pos = i + 1;
} else {
break;
}
}
}
for i in (insert_pos..self.count).rev() {
self.transforms[i + 1] = self.transforms[i].take();
}
self.transforms[insert_pos] = Some(transform);
self.count += 1;
if self.count < N {
self.head = self.count;
} else {
self.head = 0; }
} else {
self.transforms[self.head] = Some(transform);
self.head = (self.head + 1) % N;
}
}
pub fn get_latest_transform(&self) -> Option<&StampedTransform<T>> {
if self.count == 0 {
return None;
}
let mut latest_time = None;
let mut latest_idx = 0;
for i in 0..self.count.min(N) {
if let Some(ref t) = self.transforms[i]
&& (latest_time.is_none() || t.stamp > latest_time.unwrap())
{
latest_time = Some(t.stamp);
latest_idx = i;
}
}
self.transforms[latest_idx].as_ref()
}
pub fn get_time_range(&self) -> Option<CuTimeRange> {
if self.count == 0 {
return None;
}
let mut min_time = None;
let mut max_time = None;
for i in 0..self.count.min(N) {
if let Some(ref t) = self.transforms[i] {
match (min_time, max_time) {
(None, None) => {
min_time = Some(t.stamp);
max_time = Some(t.stamp);
}
(Some(min), Some(max)) => {
if t.stamp < min {
min_time = Some(t.stamp);
}
if t.stamp > max {
max_time = Some(t.stamp);
}
}
_ => unreachable!(),
}
}
}
Some(CuTimeRange {
start: min_time.unwrap(),
end: max_time.unwrap(),
})
}
pub fn get_transforms_in_range(
&self,
start_time: CuTime,
end_time: CuTime,
) -> Vec<&StampedTransform<T>> {
let mut result = Vec::new();
for i in 0..self.count.min(N) {
if let Some(ref t) = self.transforms[i]
&& t.stamp >= start_time
&& t.stamp <= end_time
{
result.push(t);
}
}
result.sort_by_key(|t| t.stamp);
result
}
pub fn get_closest_transform(&self, time: CuTime) -> Option<&StampedTransform<T>> {
if self.count == 0 {
return None;
}
let mut closest_diff = None;
let mut closest_idx = 0;
for i in 0..self.count.min(N) {
if let Some(ref t) = self.transforms[i] {
let diff = if t.stamp.as_nanos() > time.as_nanos() {
t.stamp.as_nanos() - time.as_nanos()
} else {
time.as_nanos() - t.stamp.as_nanos()
};
if closest_diff.is_none() || diff < closest_diff.unwrap() {
closest_diff = Some(diff);
closest_idx = i;
}
}
}
self.transforms[closest_idx].as_ref()
}
}
impl<T: Copy + Debug + Default + 'static, const N: usize> Default for ConstTransformBuffer<T, N> {
fn default() -> Self {
Self::new()
}
}
impl<T: Copy + Debug + Default + 'static> TransformBufferInternal<T> {
fn new() -> Self {
Self::with_capacity(DEFAULT_CACHE_SIZE)
}
fn with_capacity(capacity: usize) -> Self {
Self {
transforms: VecDeque::with_capacity(capacity),
max_capacity: capacity,
}
}
fn add_transform(&mut self, transform: StampedTransform<T>) {
let pos = self
.transforms
.partition_point(|t| t.stamp <= transform.stamp);
self.transforms.insert(pos, transform);
while self.transforms.len() > self.max_capacity {
self.transforms.pop_front();
}
}
fn get_latest_transform(&self) -> Option<&StampedTransform<T>> {
self.transforms.back()
}
fn get_time_range(&self) -> Option<CuTimeRange> {
if self.transforms.is_empty() {
return None;
}
Some(CuTimeRange {
start: self.transforms.front().unwrap().stamp,
end: self.transforms.back().unwrap().stamp,
})
}
fn get_transforms_in_range(
&self,
start_time: CuTime,
end_time: CuTime,
) -> Vec<&StampedTransform<T>> {
self.transforms
.iter()
.filter(|t| t.stamp >= start_time && t.stamp <= end_time)
.collect()
}
fn get_closest_transform(&self, time: CuTime) -> Option<&StampedTransform<T>> {
if self.transforms.is_empty() {
return None;
}
let pos = self.transforms.partition_point(|t| t.stamp <= time);
match pos {
0 => self.transforms.front(),
p if p == self.transforms.len() => self.transforms.back(),
p => {
let before = &self.transforms[p - 1];
let after = &self.transforms[p];
if time.as_nanos() - before.stamp.as_nanos()
< after.stamp.as_nanos() - time.as_nanos()
{
Some(before)
} else {
Some(after)
}
}
}
}
}
impl<T: Copy + Debug + Default + 'static> TransformBuffer<T> {
pub fn new() -> Self {
Self {
buffer: Arc::new(RwLock::new(TransformBufferInternal::new())),
}
}
pub fn with_capacity(capacity: usize) -> Self {
Self {
buffer: Arc::new(RwLock::new(TransformBufferInternal::with_capacity(
capacity,
))),
}
}
pub fn add_transform(&self, transform: StampedTransform<T>) {
let mut buffer = self.buffer.write().unwrap();
buffer.add_transform(transform);
}
pub fn get_latest_transform(&self) -> Option<StampedTransform<T>> {
let buffer = self.buffer.read().unwrap();
buffer.get_latest_transform().cloned()
}
pub fn get_time_range(&self) -> Option<CuTimeRange> {
let buffer = self.buffer.read().unwrap();
buffer.get_time_range()
}
pub fn get_transforms_in_range(
&self,
start_time: CuTime,
end_time: CuTime,
) -> Vec<StampedTransform<T>> {
let buffer = self.buffer.read().unwrap();
buffer
.get_transforms_in_range(start_time, end_time)
.into_iter()
.cloned()
.collect()
}
pub fn get_closest_transform(&self, time: CuTime) -> Option<StampedTransform<T>> {
let buffer = self.buffer.read().unwrap();
buffer.get_closest_transform(time).cloned()
}
pub fn get_transforms_around(
&self,
time: CuTime,
) -> Option<(StampedTransform<T>, StampedTransform<T>)> {
let buffer = self.buffer.read().unwrap();
if buffer.transforms.len() < 2 {
return None;
}
let pos = buffer.transforms.partition_point(|t| t.stamp <= time);
match pos {
0 => Some((buffer.transforms[0].clone(), buffer.transforms[1].clone())),
p if p >= buffer.transforms.len() => {
let len = buffer.transforms.len();
Some((
buffer.transforms[len - 2].clone(),
buffer.transforms[len - 1].clone(),
))
}
p => Some((
buffer.transforms[p - 1].clone(),
buffer.transforms[p].clone(),
)),
}
}
pub fn compute_velocity_at_time(
&self,
time: CuTime,
) -> Option<crate::velocity::VelocityTransform<T>>
where
T: Default
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ num_traits::NumCast,
{
let (first, second) = self.get_transforms_around(time)?;
let (before, after) = ordered_by_stamp(first, second);
after.compute_velocity(&before)
}
}
impl<T: Copy + std::fmt::Debug + Default + 'static> Default for TransformBuffer<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Copy + Debug + Default + 'static, const N: usize> ConstTransformBufferSync<T, N> {
pub fn new() -> Self {
Self {
buffer: Arc::new(RwLock::new(ConstTransformBuffer::new())),
}
}
pub fn add_transform(&self, transform: StampedTransform<T>) {
let mut buffer = self.buffer.write().unwrap();
buffer.add_transform(transform);
}
pub fn get_latest_transform(&self) -> Option<StampedTransform<T>> {
let buffer = self.buffer.read().unwrap();
buffer.get_latest_transform().cloned()
}
pub fn get_time_range(&self) -> Option<CuTimeRange> {
let buffer = self.buffer.read().unwrap();
buffer.get_time_range()
}
pub fn get_transforms_in_range(
&self,
start_time: CuTime,
end_time: CuTime,
) -> Vec<StampedTransform<T>> {
let buffer = self.buffer.read().unwrap();
buffer
.get_transforms_in_range(start_time, end_time)
.into_iter()
.cloned()
.collect()
}
pub fn get_closest_transform(&self, time: CuTime) -> Option<StampedTransform<T>> {
let buffer = self.buffer.read().unwrap();
buffer.get_closest_transform(time).cloned()
}
pub fn get_transforms_around(
&self,
time: CuTime,
) -> Option<(StampedTransform<T>, StampedTransform<T>)> {
let buffer = self.buffer.read().unwrap();
if buffer.count < 2 {
return None;
}
let mut best_pair: Option<(usize, usize)> = None;
let mut best_distance = u64::MAX;
for i in 0..buffer.count.min(N) {
if let Some(ref t1) = buffer.transforms[i] {
for j in (i + 1)..buffer.count.min(N) {
if let Some(ref t2) = buffer.transforms[j] {
let (earlier, later) = if t1.stamp <= t2.stamp {
(t1, t2)
} else {
(t2, t1)
};
let distance = if time <= earlier.stamp {
earlier.stamp.as_nanos() - time.as_nanos()
} else if time >= later.stamp {
time.as_nanos() - later.stamp.as_nanos()
} else {
0
};
if distance < best_distance {
best_distance = distance;
best_pair = Some((i, j));
}
}
}
}
}
if let Some((i, j)) = best_pair {
let t1 = buffer.transforms[i].as_ref()?.clone();
let t2 = buffer.transforms[j].as_ref()?.clone();
Some(ordered_by_stamp(t1, t2))
} else {
None
}
}
pub fn compute_velocity_at_time(
&self,
time: CuTime,
) -> Option<crate::velocity::VelocityTransform<T>>
where
T: Default
+ std::ops::Add<Output = T>
+ std::ops::Sub<Output = T>
+ std::ops::Mul<Output = T>
+ std::ops::Div<Output = T>
+ num_traits::NumCast,
{
let (first, second) = self.get_transforms_around(time)?;
let (before, after) = ordered_by_stamp(first, second);
after.compute_velocity(&before)
}
}
impl<T: Copy + Debug + Default + 'static, const N: usize> Default
for ConstTransformBufferSync<T, N>
{
fn default() -> Self {
Self::new()
}
}
pub struct TransformStore<T: Copy + Debug + Default + 'static> {
buffers: DashMap<(FrameIdString, FrameIdString), TransformBuffer<T>>,
}
impl<T: Copy + Debug + Default + 'static> TransformStore<T> {
pub fn new() -> Self {
Self {
buffers: DashMap::new(),
}
}
pub fn get_or_create_buffer(&self, parent: &str, child: &str) -> TransformBuffer<T> {
let parent_frame = FrameIdString::from(parent).expect("Parent frame name too long");
let child_frame = FrameIdString::from(child).expect("Child frame name too long");
self.buffers
.entry((parent_frame, child_frame))
.or_insert_with(|| TransformBuffer::new())
.clone()
}
pub fn add_transform(&self, transform: StampedTransform<T>) {
let buffer = self.get_or_create_buffer(&transform.parent_frame, &transform.child_frame);
buffer.add_transform(transform);
}
pub fn get_buffer(&self, parent: &str, child: &str) -> Option<TransformBuffer<T>> {
let parent_frame = FrameIdString::from(parent).ok()?;
let child_frame = FrameIdString::from(child).ok()?;
self.buffers
.get(&(parent_frame, child_frame))
.map(|entry| entry.clone())
}
}
impl<T: Copy + Debug + Default + 'static> Default for TransformStore<T> {
fn default() -> Self {
Self::new()
}
}
pub struct ConstTransformStore<T: Copy + Debug + Default + 'static, const N: usize> {
buffers: DashMap<(FrameIdString, FrameIdString), ConstTransformBufferSync<T, N>>,
}
impl<T: Copy + Debug + Default + 'static, const N: usize> ConstTransformStore<T, N> {
pub fn new() -> Self {
Self {
buffers: DashMap::new(),
}
}
pub fn get_or_create_buffer(
&self,
parent: &str,
child: &str,
) -> ConstTransformBufferSync<T, N> {
let parent_frame = FrameIdString::from(parent).expect("Parent frame name too long");
let child_frame = FrameIdString::from(child).expect("Child frame name too long");
self.buffers
.entry((parent_frame, child_frame))
.or_insert_with(|| ConstTransformBufferSync::new())
.clone()
}
pub fn add_transform(&self, transform: StampedTransform<T>) {
let buffer = self.get_or_create_buffer(&transform.parent_frame, &transform.child_frame);
buffer.add_transform(transform);
}
pub fn get_buffer(&self, parent: &str, child: &str) -> Option<ConstTransformBufferSync<T, N>> {
let parent_frame = FrameIdString::from(parent).ok()?;
let child_frame = FrameIdString::from(child).ok()?;
self.buffers
.get(&(parent_frame, child_frame))
.map(|entry| entry.clone())
}
}
impl<T: Copy + Debug + Default + 'static, const N: usize> Default for ConstTransformStore<T, N> {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_approx_eq(actual: f32, expected: f32, epsilon: f32) {
let diff = (actual - expected).abs();
assert!(
diff <= epsilon,
"expected {expected}, got {actual}, difference {diff} exceeds epsilon {epsilon}",
);
}
use cu29::clock::CuDuration;
#[test]
fn test_add_transform() {
let buffer = TransformBuffer::<f32>::new();
let transform = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform);
let latest = buffer.get_latest_transform();
assert!(latest.is_some());
}
#[test]
fn test_time_ordering() {
let buffer = TransformBuffer::<f32>::new();
let transform1 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(2000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform3 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(3000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform1);
buffer.add_transform(transform2);
buffer.add_transform(transform3);
let range = buffer.get_time_range().unwrap();
assert_eq!(range.start.as_nanos(), 1000);
assert_eq!(range.end.as_nanos(), 3000);
let transforms = buffer.get_transforms_in_range(CuDuration(1500), CuDuration(2500));
assert_eq!(transforms.len(), 1);
assert_eq!(transforms[0].stamp.as_nanos(), 2000);
}
#[test]
fn test_capacity_limit() {
let buffer = TransformBuffer::<f32>::with_capacity(2);
let transform1 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(2000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform3 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(3000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform1);
buffer.add_transform(transform2);
buffer.add_transform(transform3);
let range = buffer.get_time_range().unwrap();
assert_eq!(range.start.as_nanos(), 2000);
assert_eq!(range.end.as_nanos(), 3000);
}
#[test]
fn test_get_closest_transform() {
let buffer = TransformBuffer::<f32>::new();
let transform1 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(3000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform1);
buffer.add_transform(transform2);
let closest = buffer.get_closest_transform(CuDuration(1000));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 1000);
let closest = buffer.get_closest_transform(CuDuration(500));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 1000);
let closest = buffer.get_closest_transform(CuDuration(4000));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 3000);
let closest = buffer.get_closest_transform(CuDuration(1600));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 1000);
let closest = buffer.get_closest_transform(CuDuration(2600));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 3000);
}
#[test]
fn test_transform_store() {
let store = TransformStore::<f32>::new();
let transform = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
store.add_transform(transform.clone());
let buffer = store.get_buffer("world", "robot").unwrap();
let closest = buffer.get_closest_transform(CuDuration(1000));
assert!(closest.is_some());
let missing = store.get_buffer("world", "camera");
assert!(missing.is_none());
let _ = store.get_or_create_buffer("world", "camera");
assert!(store.get_buffer("world", "camera").is_some());
}
#[test]
fn test_compute_velocity() {
let mut transform1 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(1_000_000_000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let mut transform2 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(2_000_000_000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
transform1.transform = Transform3D::from_matrix([
[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0], ]);
transform2.transform = Transform3D::from_matrix([
[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [1.0, 2.0, 0.0, 1.0], ]);
let velocity = transform2.compute_velocity(&transform1);
assert!(velocity.is_some());
let vel = velocity.unwrap();
assert_approx_eq(vel.linear[0], 1.0, 1e-5);
assert_approx_eq(vel.linear[1], 2.0, 1e-5);
assert_approx_eq(vel.linear[2], 0.0, 1e-5);
assert_approx_eq(vel.angular[0], 0.0, 1e-5);
assert_approx_eq(vel.angular[1], 0.0, 1e-5);
assert_approx_eq(vel.angular[2], 0.0, 1e-5);
}
#[test]
fn test_velocity_failure_cases() {
let transform1 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(2000),
parent_frame: FrameIdString::from("different").unwrap(), child_frame: FrameIdString::from("robot").unwrap(),
};
let velocity = transform2.compute_velocity(&transform1);
assert!(velocity.is_none());
let transform1 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(2000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(1000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let velocity = transform2.compute_velocity(&transform1);
assert!(velocity.is_none());
}
#[test]
fn test_get_transforms_around() {
let buffer = TransformBuffer::<f32>::new();
let transform1 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(2000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform3 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(3000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform1);
buffer.add_transform(transform2);
buffer.add_transform(transform3);
let transforms = buffer.get_transforms_around(CuDuration(2500));
assert!(transforms.is_some());
let (t1, t2) = transforms.unwrap();
assert_eq!(t1.stamp.as_nanos(), 2000); assert_eq!(t2.stamp.as_nanos(), 3000);
let transforms = buffer.get_transforms_around(CuDuration(500));
assert!(transforms.is_some());
let (t1, t2) = transforms.unwrap();
assert_eq!(t1.stamp.as_nanos(), 1000); assert_eq!(t2.stamp.as_nanos(), 2000);
let transforms = buffer.get_transforms_around(CuDuration(4000));
assert!(transforms.is_some());
let (t1, t2) = transforms.unwrap();
assert_eq!(t1.stamp.as_nanos(), 2000); assert_eq!(t2.stamp.as_nanos(), 3000); }
#[test]
fn test_compute_velocity_from_buffer() {
let buffer = TransformBuffer::<f32>::new();
let mut transform1 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(1_000_000_000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let mut transform2 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(2_000_000_000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
transform1.transform = Transform3D::from_matrix([
[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0], ]);
transform2.transform = Transform3D::from_matrix([
[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [2.0, 0.0, 0.0, 1.0], ]);
buffer.add_transform(transform1);
buffer.add_transform(transform2);
let velocity = buffer.compute_velocity_at_time(CuDuration(1_500_000_000)); assert!(velocity.is_some());
let vel = velocity.unwrap();
assert_approx_eq(vel.linear[0], 2.0, 1e-5);
assert_approx_eq(vel.linear[1], 0.0, 1e-5);
assert_approx_eq(vel.linear[2], 0.0, 1e-5);
}
#[test]
fn test_compute_angular_velocity() {
let buffer = TransformBuffer::<f32>::new();
let mut transform1 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(1_000_000_000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
transform1.transform = Transform3D::from_matrix([
[1.0, 0.0, 0.0, 0.0],
[0.0, 1.0, 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
]);
let mut transform2 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(2_000_000_000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
transform2.transform = Transform3D::from_matrix([
[0.0, -1.0, 0.0, 0.0], [1.0, 0.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0], ]);
buffer.add_transform(transform1);
buffer.add_transform(transform2);
let velocity = buffer.compute_velocity_at_time(CuDuration(1_500_000_000)); assert!(velocity.is_some());
let vel = velocity.unwrap();
assert_approx_eq(vel.angular[0], 0.0, 1e-5);
assert_approx_eq(vel.angular[1], 0.0, 1e-5);
assert_approx_eq(vel.angular[2], 1.0, 1e-5); }
#[test]
fn test_const_transform_buffer_basic() {
let mut buffer = ConstTransformBuffer::<f32, 10>::new();
let transform = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform);
let latest = buffer.get_latest_transform();
assert!(latest.is_some());
assert_eq!(latest.unwrap().stamp.as_nanos(), 1000);
}
#[test]
fn test_const_transform_buffer_ordering() {
let mut buffer = ConstTransformBuffer::<f32, 10>::new();
let transform1 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(2000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform3 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(3000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform1);
buffer.add_transform(transform2);
buffer.add_transform(transform3);
let range = buffer.get_time_range().unwrap();
assert_eq!(range.start.as_nanos(), 1000);
assert_eq!(range.end.as_nanos(), 3000);
let transforms = buffer.get_transforms_in_range(CuDuration(1500), CuDuration(2500));
assert_eq!(transforms.len(), 1);
assert_eq!(transforms[0].stamp.as_nanos(), 2000);
}
#[test]
fn test_const_transform_buffer_capacity() {
let mut buffer = ConstTransformBuffer::<f32, 2>::new();
let transform1 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(2000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform3 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(3000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform1);
buffer.add_transform(transform2);
buffer.add_transform(transform3);
let latest = buffer.get_latest_transform();
assert!(latest.is_some());
let closest = buffer.get_closest_transform(CuDuration(2500));
assert!(closest.is_some());
}
#[test]
fn test_const_transform_buffer_closest() {
let mut buffer = ConstTransformBuffer::<f32, 10>::new();
let transform1 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let transform2 = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(3000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform1);
buffer.add_transform(transform2);
let closest = buffer.get_closest_transform(CuDuration(1000));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 1000);
let closest = buffer.get_closest_transform(CuDuration(500));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 1000);
let closest = buffer.get_closest_transform(CuDuration(4000));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 3000);
let closest = buffer.get_closest_transform(CuDuration(1900));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 1000);
let closest = buffer.get_closest_transform(CuDuration(2100));
assert!(closest.is_some());
assert_eq!(closest.unwrap().stamp.as_nanos(), 3000); }
#[test]
fn test_const_transform_buffer_sync_basic() {
let buffer = ConstTransformBufferSync::<f32, 10>::new();
let transform = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
buffer.add_transform(transform);
let latest = buffer.get_latest_transform();
assert!(latest.is_some());
assert_eq!(latest.unwrap().stamp.as_nanos(), 1000);
}
#[test]
fn test_const_transform_buffer_sync_velocity() {
let buffer = ConstTransformBufferSync::<f32, 10>::new();
let mut transform1 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(1_000_000_000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
let mut transform2 = StampedTransform {
transform: Transform3D::<f32>::default(),
stamp: CuDuration(2_000_000_000), parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
transform1.transform = Transform3D::from_matrix([
[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [0.0, 0.0, 0.0, 1.0], ]);
transform2.transform = Transform3D::from_matrix([
[1.0, 0.0, 0.0, 0.0], [0.0, 1.0, 0.0, 0.0], [0.0, 0.0, 1.0, 0.0], [3.0, 0.0, 0.0, 1.0], ]);
buffer.add_transform(transform1);
buffer.add_transform(transform2);
let velocity = buffer.compute_velocity_at_time(CuDuration(1_500_000_000)); assert!(velocity.is_some());
let vel = velocity.unwrap();
assert_approx_eq(vel.linear[0], 3.0, 1e-5);
assert_approx_eq(vel.linear[1], 0.0, 1e-5);
assert_approx_eq(vel.linear[2], 0.0, 1e-5);
}
#[test]
fn test_const_transform_store() {
let store = ConstTransformStore::<f32, 10>::new();
let transform = StampedTransform {
transform: Transform3D::default(),
stamp: CuDuration(1000),
parent_frame: FrameIdString::from("world").unwrap(),
child_frame: FrameIdString::from("robot").unwrap(),
};
store.add_transform(transform.clone());
let buffer = store.get_buffer("world", "robot").unwrap();
let closest = buffer.get_closest_transform(CuDuration(1000));
assert!(closest.is_some());
let missing = store.get_buffer("world", "camera");
assert!(missing.is_none());
let _ = store.get_or_create_buffer("world", "camera");
assert!(store.get_buffer("world", "camera").is_some());
}
}