Skip to main content

apex_solver/observers/
conversions.rs

1//! Conversion utilities for Rerun visualization types.
2//!
3//! This module provides clean conversions from apex-solver's manifold types
4//! (SE2, SE3, SO2, SO3, Rn) to Rerun's visualization types. These conversions
5//! enable seamless integration with Rerun's real-time visualization system.
6//!
7//! # Design Philosophy
8//!
9//! We use extension traits to provide ergonomic conversion methods while
10//! respecting Rust's orphan rule. This approach provides:
11//! - Clear, self-documenting method names
12//! - Better IDE autocomplete and discoverability
13//! - Type-safe conversions with zero runtime overhead
14//! - Consistent color schemes (red/green/blue for x/y/z axes)
15//!
16//! # Examples
17//!
18//! ## Single Pose Conversion
19//!
20//! ```no_run
21//! # #[cfg(feature = "visualization")]
22//! # {
23//! use apex_solver::manifold::se3::SE3;
24//! use apex_solver::observers::RerunConvert3D;
25//!
26//! let pose = SE3::identity();
27//! let transform = pose.to_rerun_transform();
28//! # }
29//! ```
30//!
31//! ## Batch Conversion
32//!
33//! ```no_run
34//! # #[cfg(feature = "visualization")]
35//! # {
36//! use apex_solver::manifold::se3::SE3;
37//! use apex_solver::observers::CollectRerun3D;
38//!
39//! let poses = vec![SE3::identity(), SE3::identity()];
40//! let points = poses.iter().collect_points3d();
41//! # }
42//! ```
43
44#[cfg(feature = "visualization")]
45use apex_manifolds::{se2::SE2, se3::SE3};
46#[cfg(feature = "visualization")]
47use rerun::{Arrows2D, Arrows3D, Points2D, Points3D, Transform3D, Vec2D, Vec3D};
48
49// ============================================================================
50// Consolidated Rerun conversion traits
51// ============================================================================
52
53/// Rerun conversion methods for SE3 types.
54///
55/// Provides ergonomic conversion from SE3 poses to all Rerun 3D primitive types.
56#[cfg(feature = "visualization")]
57pub trait RerunConvert3D {
58    /// Convert this SE3 pose to a Rerun Transform3D (translation + rotation).
59    fn to_rerun_transform(&self) -> Transform3D;
60
61    /// Extract the translation component as a Rerun Vec3D.
62    fn to_rerun_vec3d(&self) -> Vec3D;
63
64    /// Convert this SE3 pose to a single point in Rerun Points3D format.
65    fn to_rerun_points3d(&self) -> Points3D;
66
67    /// Convert this SE3 pose to coordinate frame arrows in Rerun.
68    ///
69    /// Creates three arrows (X=red, Y=green, Z=blue) showing the pose's
70    /// orientation, rooted at the pose's translation.
71    fn to_rerun_arrows3d(&self) -> Arrows3D;
72}
73
74/// Rerun conversion methods for SE2 types.
75///
76/// Provides ergonomic conversion from SE2 poses to all Rerun 2D primitive types.
77#[cfg(feature = "visualization")]
78pub trait RerunConvert2D {
79    /// Extract the 2D translation component as a Rerun Vec2D.
80    fn to_rerun_vec2d(&self) -> Vec2D;
81
82    /// Convert this SE2 pose to a single point in Rerun Points2D format.
83    fn to_rerun_points2d(&self) -> Points2D;
84
85    /// Convert this SE2 pose to coordinate frame arrows in Rerun.
86    ///
87    /// Creates two arrows (X=red, Y=green) showing the pose's orientation,
88    /// rooted at the pose's translation.
89    fn to_rerun_arrows2d(&self) -> Arrows2D;
90
91    /// Convert this SE2 pose to a 3D transform at the z=0 plane.
92    ///
93    /// Useful for visualizing 2D poses in a 3D viewer.
94    fn to_rerun_transform_3d(&self) -> Transform3D;
95}
96
97/// Batch Rerun collection for SE3 iterators.
98#[cfg(feature = "visualization")]
99pub trait CollectRerun3D<'a> {
100    /// Collect SE3 poses into a Points3D cloud (translation components only).
101    fn collect_points3d(self) -> Points3D;
102
103    /// Collect SE3 poses into coordinate frame arrows.
104    ///
105    /// Creates three arrows (X=red, Y=green, Z=blue) for each pose.
106    fn collect_arrows3d(self) -> Arrows3D;
107}
108
109/// Batch Rerun collection for SE2 iterators.
110#[cfg(feature = "visualization")]
111pub trait CollectRerun2D<'a> {
112    /// Collect SE2 poses into a Points2D cloud (translation components only).
113    fn collect_points2d(self) -> Points2D;
114
115    /// Collect SE2 poses into coordinate frame arrows.
116    ///
117    /// Creates two arrows (X=red, Y=green) for each pose.
118    fn collect_arrows2d(self) -> Arrows2D;
119}
120
121// ============================================================================
122// SE3 Implementation
123// ============================================================================
124
125#[cfg(feature = "visualization")]
126impl RerunConvert3D for SE3 {
127    fn to_rerun_transform(&self) -> Transform3D {
128        let trans = self.translation();
129        let rot = self.rotation_quaternion();
130
131        let position =
132            rerun::external::glam::Vec3::new(trans.x as f32, trans.y as f32, trans.z as f32);
133
134        let rotation = rerun::external::glam::Quat::from_xyzw(
135            rot.as_ref().i as f32,
136            rot.as_ref().j as f32,
137            rot.as_ref().k as f32,
138            rot.as_ref().w as f32,
139        );
140
141        Transform3D::from_translation_rotation(position, rotation)
142    }
143
144    fn to_rerun_vec3d(&self) -> Vec3D {
145        let trans = self.translation();
146        Vec3D::new(trans.x as f32, trans.y as f32, trans.z as f32)
147    }
148
149    fn to_rerun_points3d(&self) -> Points3D {
150        let vec = self.to_rerun_vec3d();
151        Points3D::new([vec])
152    }
153
154    fn to_rerun_arrows3d(&self) -> Arrows3D {
155        let rot_quat = self.rotation_quaternion();
156        let rot_mat = rot_quat.to_rotation_matrix();
157        let trans = self.translation();
158
159        let x_axis = [
160            rot_mat[(0, 0)] as f32,
161            rot_mat[(1, 0)] as f32,
162            rot_mat[(2, 0)] as f32,
163        ];
164        let y_axis = [
165            rot_mat[(0, 1)] as f32,
166            rot_mat[(1, 1)] as f32,
167            rot_mat[(2, 1)] as f32,
168        ];
169        let z_axis = [
170            rot_mat[(0, 2)] as f32,
171            rot_mat[(1, 2)] as f32,
172            rot_mat[(2, 2)] as f32,
173        ];
174
175        let origin = [trans.x as f32, trans.y as f32, trans.z as f32];
176
177        Arrows3D::from_vectors([x_axis, y_axis, z_axis])
178            .with_origins([origin, origin, origin])
179            .with_colors([[255, 0, 0], [0, 255, 0], [0, 0, 255]]) // RGB for XYZ
180    }
181}
182
183// ============================================================================
184// SE2 Implementation
185// ============================================================================
186
187#[cfg(feature = "visualization")]
188impl RerunConvert2D for SE2 {
189    fn to_rerun_vec2d(&self) -> Vec2D {
190        Vec2D::new(self.x() as f32, self.y() as f32)
191    }
192
193    fn to_rerun_points2d(&self) -> Points2D {
194        let vec = self.to_rerun_vec2d();
195        Points2D::new([vec])
196    }
197
198    fn to_rerun_arrows2d(&self) -> Arrows2D {
199        let rot_mat = self.rotation_matrix();
200
201        let x_axis = [rot_mat[(0, 0)] as f32, rot_mat[(1, 0)] as f32];
202        let y_axis = [rot_mat[(0, 1)] as f32, rot_mat[(1, 1)] as f32];
203
204        let origin = [self.x() as f32, self.y() as f32];
205
206        Arrows2D::from_vectors([x_axis, y_axis])
207            .with_origins([origin, origin])
208            .with_colors([[255, 0, 0], [0, 255, 0]]) // Red/Green for X/Y
209    }
210
211    fn to_rerun_transform_3d(&self) -> Transform3D {
212        let position = rerun::external::glam::Vec3::new(self.x() as f32, self.y() as f32, 0.0);
213
214        let angle = self.angle();
215        let half_angle = (angle / 2.0) as f32;
216        let rotation =
217            rerun::external::glam::Quat::from_xyzw(0.0, 0.0, half_angle.sin(), half_angle.cos());
218
219        Transform3D::from_translation_rotation(position, rotation)
220    }
221}
222
223// ============================================================================
224// Batch SE3 Iterator Implementations
225// ============================================================================
226
227#[cfg(feature = "visualization")]
228impl<'a, I> CollectRerun3D<'a> for I
229where
230    I: Iterator<Item = &'a SE3>,
231{
232    fn collect_points3d(self) -> Points3D {
233        let points: Vec<Vec3D> = self.map(|se3| se3.to_rerun_vec3d()).collect();
234        Points3D::new(points)
235    }
236
237    fn collect_arrows3d(self) -> Arrows3D {
238        let mut vectors = Vec::new();
239        let mut origins = Vec::new();
240        let mut colors = Vec::new();
241
242        for se3 in self {
243            let rot_quat = se3.rotation_quaternion();
244            let rot_mat = rot_quat.to_rotation_matrix();
245            let trans = se3.translation();
246
247            let x_axis = [
248                rot_mat[(0, 0)] as f32,
249                rot_mat[(1, 0)] as f32,
250                rot_mat[(2, 0)] as f32,
251            ];
252            let y_axis = [
253                rot_mat[(0, 1)] as f32,
254                rot_mat[(1, 1)] as f32,
255                rot_mat[(2, 1)] as f32,
256            ];
257            let z_axis = [
258                rot_mat[(0, 2)] as f32,
259                rot_mat[(1, 2)] as f32,
260                rot_mat[(2, 2)] as f32,
261            ];
262
263            let origin = [trans.x as f32, trans.y as f32, trans.z as f32];
264
265            vectors.push(x_axis);
266            vectors.push(y_axis);
267            vectors.push(z_axis);
268            origins.push(origin);
269            origins.push(origin);
270            origins.push(origin);
271            colors.push([255, 0, 0]); // X = red
272            colors.push([0, 255, 0]); // Y = green
273            colors.push([0, 0, 255]); // Z = blue
274        }
275
276        Arrows3D::from_vectors(vectors)
277            .with_origins(origins)
278            .with_colors(colors)
279    }
280}
281
282// ============================================================================
283// Batch SE2 Iterator Implementations
284// ============================================================================
285
286#[cfg(feature = "visualization")]
287impl<'a, I> CollectRerun2D<'a> for I
288where
289    I: Iterator<Item = &'a SE2>,
290{
291    fn collect_points2d(self) -> Points2D {
292        let points: Vec<Vec2D> = self.map(|se2| se2.to_rerun_vec2d()).collect();
293        Points2D::new(points)
294    }
295
296    fn collect_arrows2d(self) -> Arrows2D {
297        let mut vectors = Vec::new();
298        let mut origins = Vec::new();
299        let mut colors = Vec::new();
300
301        for se2 in self {
302            let rot_mat = se2.rotation_matrix();
303
304            let x_axis = [rot_mat[(0, 0)] as f32, rot_mat[(1, 0)] as f32];
305            let y_axis = [rot_mat[(0, 1)] as f32, rot_mat[(1, 1)] as f32];
306
307            let origin = [se2.x() as f32, se2.y() as f32];
308
309            vectors.push(x_axis);
310            vectors.push(y_axis);
311            origins.push(origin);
312            origins.push(origin);
313            colors.push([255, 0, 0]); // X = red
314            colors.push([0, 255, 0]); // Y = green
315        }
316
317        Arrows2D::from_vectors(vectors)
318            .with_origins(origins)
319            .with_colors(colors)
320    }
321}
322
323#[cfg(test)]
324#[cfg(feature = "visualization")]
325mod tests {
326    use super::*;
327
328    #[test]
329    fn test_se3_to_vec3d() {
330        use apex_manifolds::se3::SE3;
331
332        let pose = SE3::identity();
333        let vec = pose.to_rerun_vec3d();
334
335        assert_eq!(vec.x(), 0.0);
336        assert_eq!(vec.y(), 0.0);
337        assert_eq!(vec.z(), 0.0);
338    }
339
340    #[test]
341    fn test_se2_to_vec2d() {
342        use apex_manifolds::se2::SE2;
343
344        let pose = SE2::identity();
345        let vec = pose.to_rerun_vec2d();
346
347        assert_eq!(vec.x(), 0.0);
348        assert_eq!(vec.y(), 0.0);
349    }
350
351    #[test]
352    fn test_se3_collection_to_points() {
353        use apex_manifolds::se3::SE3;
354
355        let poses = [SE3::identity(), SE3::identity(), SE3::identity()];
356        let points = poses.iter().collect_points3d();
357
358        let _ = points;
359    }
360
361    #[test]
362    fn test_se2_collection_to_arrows() {
363        use apex_manifolds::se2::SE2;
364
365        let poses = [SE2::identity(), SE2::identity()];
366        let arrows = poses.iter().collect_arrows2d();
367
368        let _ = arrows;
369    }
370}