1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
//! Traits for locating values in a frame and applying transforms to them.
use crate::;
/// A trait for types that are localized in a specific coordinate frame at a specific time.
///
/// This trait provides frame and timestamp introspection, enabling automatic transform
/// lookup via [`Registry::get_transform_for`](crate::Registry::get_transform_for).
///
/// Separate from [`Transformable`] so that types without frame/timestamp metadata
/// can still implement `Transformable` independently.
///
/// # Examples
///
/// ```
/// use transforms::{
/// Localized,
/// geometry::{Point, Quaternion, Vector3},
/// time::Timestamp,
/// };
///
/// let point: Point = Point::new(
/// Vector3::new(1.0, 0.0, 0.0),
/// Quaternion::identity(),
/// Timestamp::zero(),
/// "camera",
/// );
///
/// assert_eq!(point.frame(), "camera");
/// ```
/// A trait for types that can be transformed between different coordinate frames.
///
/// This trait provides functionality to apply spatial transformations to objects,
/// typically used in robotics and computer vision applications. The transformations
/// follow the common robotics convention where transforms are considered from child
/// to parent frame (e.g., from sensor frame to base frame, or from base frame to
/// map frame).
///
/// # Frame Convention
///
/// In robotics, it's common to transform data from sensor reference frames "up" to
/// base or map reference frames. For example:
/// - A camera's data might need to be transformed from the camera frame to the robot's base frame
/// - Lidar points might need to be transformed from the lidar frame to the map frame
///
/// This trait follows this convention, where transforms are applied from child frame
/// to parent frame. The child frame is typically the more specific/local frame (e.g.,
/// a sensor frame), while the parent frame is typically the more general/global frame
/// (e.g., map or world frame).
///
/// # Contract
///
/// An implementation owes the rigid-body map, in this order: every bound
/// position `p` becomes
/// `transform.rotation().rotate_vector(p) + transform.translation()` —
/// rotate first, then translate — and every orientation `q` becomes
/// `transform.rotation() * q`, the transform's rotation on the left. A
/// free vector — a velocity, a surface normal — takes the rotation only,
/// and owes no translation. Where the object carries them, as
/// [`Point`](crate::geometry::Point) does, its frame becomes the
/// transform's parent frame; timestamps are checked, never rewritten.
/// The reversed variants compile, and each has a blind spot that keeps
/// weak tests green: translating before rotating agrees with the
/// contract until a real rotation meets a non-zero translation, and
/// `q * transform.rotation()` agrees until the object's own orientation
/// is non-identity and does not commute with the transform's. Beyond the
/// blind spot both produce a silent wrong answer, never a loud failure.
/// `Point`'s implementation is the reference, and the suite pins both
/// orders.
///
/// # Precondition
///
/// An implementation applies the transform's geometry as given; it checks
/// frames and time, not numbers. A [`Transform`] built through its
/// constructors or read through its `Deserialize` impl was checked there —
/// both reject non-finite components and non-unit rotations. One *derived*
/// from valid transforms was not: `*`, [`Transform::inverse`],
/// [`Transform::interpolate`] and registry lookups deliberately skip the
/// re-check, and composing operands at the edge of the tolerance walks past
/// it. Applying such a transform deserves a [`Transform::validate`] call
/// first: a rotation whose norm is 1.01 scales everything it touches by 2%
/// and reports success.
///
/// # Errors
///
/// Returns `TransformError` if:
/// - The frames are incompatible (transform's child frame doesn't match the object's frame)
/// - The timestamps don't match — except for static transforms (carrying
/// `Stamp::Static`, e.g. built with `Transform::static_between`), which
/// are valid for all time
/// - Other transform-specific errors occur
///
/// # Examples
///
/// ```
/// use transforms::{
/// geometry::{Point, Quaternion, Transform, Transformable, Vector3},
/// time::{Stamp, Timestamp},
/// };
///
/// let mut point: Point = Point::new(
/// Vector3::new(1.0, 0.0, 0.0),
/// Quaternion::identity(),
/// Timestamp::zero(),
/// "camera",
/// );
///
/// let transform: Transform = Transform::new(
/// "base",
/// "camera",
/// Vector3::new(0.0, 1.0, 0.0),
/// Quaternion::identity(),
/// Stamp::At(point.timestamp),
/// )
/// .unwrap();
///
/// // Transform the point from camera frame to base frame
/// point
/// .transform(&transform)
/// .expect("failed to transform point");
/// ```