openigtlink_rust/protocol/types/
position.rs

1//! POSITION message type implementation
2//!
3//! The POSITION message type is used to transfer position and orientation information.
4//! The data consists of a 3D position vector and a quaternion for orientation.
5//! This format is 19% smaller than TRANSFORM and ideal for high frame-rate tracking data.
6
7use crate::protocol::message::Message;
8use crate::error::{IgtlError, Result};
9use bytes::{Buf, BufMut};
10
11/// POSITION message containing position and quaternion orientation
12///
13/// # OpenIGTLink Specification
14/// - Message type: "POSITION" (alias: "QTRANS")
15/// - Body size: 28 bytes (7 × 4-byte floats)
16/// - Encoding: Big-endian
17/// - Position in millimeters, orientation as quaternion
18#[derive(Debug, Clone, PartialEq)]
19pub struct PositionMessage {
20    /// Position in millimeters
21    pub position: [f32; 3], // X, Y, Z
22
23    /// Orientation as quaternion
24    ///
25    /// Quaternion components in order: [ox, oy, oz, w]
26    /// where q = w + ox*i + oy*j + oz*k
27    pub quaternion: [f32; 4], // OX, OY, OZ, W
28}
29
30impl PositionMessage {
31    /// Create a new POSITION message at origin with identity orientation
32    pub fn identity() -> Self {
33        PositionMessage {
34            position: [0.0, 0.0, 0.0],
35            quaternion: [0.0, 0.0, 0.0, 1.0], // Identity quaternion
36        }
37    }
38
39    /// Create a new POSITION message with specified position and identity orientation
40    pub fn new(x: f32, y: f32, z: f32) -> Self {
41        PositionMessage {
42            position: [x, y, z],
43            quaternion: [0.0, 0.0, 0.0, 1.0],
44        }
45    }
46
47    /// Create a new POSITION message with position and quaternion
48    pub fn with_quaternion(position: [f32; 3], quaternion: [f32; 4]) -> Self {
49        PositionMessage {
50            position,
51            quaternion,
52        }
53    }
54
55    /// Get position coordinates
56    pub fn get_position(&self) -> (f32, f32, f32) {
57        (self.position[0], self.position[1], self.position[2])
58    }
59
60    /// Get quaternion components
61    pub fn get_quaternion(&self) -> (f32, f32, f32, f32) {
62        (
63            self.quaternion[0],
64            self.quaternion[1],
65            self.quaternion[2],
66            self.quaternion[3],
67        )
68    }
69}
70
71impl Message for PositionMessage {
72    fn message_type() -> &'static str {
73        "POSITION"
74    }
75
76    fn encode_content(&self) -> Result<Vec<u8>> {
77        let mut buf = Vec::with_capacity(28);
78
79        // Encode position (X, Y, Z)
80        for &coord in &self.position {
81            buf.put_f32(coord);
82        }
83
84        // Encode quaternion (OX, OY, OZ, W)
85        for &comp in &self.quaternion {
86            buf.put_f32(comp);
87        }
88
89        Ok(buf)
90    }
91
92    fn decode_content(mut data: &[u8]) -> Result<Self> {
93        if data.len() < 28 {
94            return Err(IgtlError::InvalidSize {
95                expected: 28,
96                actual: data.len(),
97            });
98        }
99
100        // Decode position
101        let position = [data.get_f32(), data.get_f32(), data.get_f32()];
102
103        // Decode quaternion
104        let quaternion = [
105            data.get_f32(),
106            data.get_f32(),
107            data.get_f32(),
108            data.get_f32(),
109        ];
110
111        Ok(PositionMessage {
112            position,
113            quaternion,
114        })
115    }
116}
117
118#[cfg(test)]
119mod tests {
120    use super::*;
121
122    #[test]
123    fn test_message_type() {
124        assert_eq!(PositionMessage::message_type(), "POSITION");
125    }
126
127    #[test]
128    fn test_identity() {
129        let pos = PositionMessage::identity();
130        assert_eq!(pos.position, [0.0, 0.0, 0.0]);
131        assert_eq!(pos.quaternion, [0.0, 0.0, 0.0, 1.0]);
132    }
133
134    #[test]
135    fn test_new() {
136        let pos = PositionMessage::new(10.0, 20.0, 30.0);
137        assert_eq!(pos.position, [10.0, 20.0, 30.0]);
138        assert_eq!(pos.quaternion, [0.0, 0.0, 0.0, 1.0]);
139    }
140
141    #[test]
142    fn test_with_quaternion() {
143        let pos = PositionMessage::with_quaternion(
144            [1.0, 2.0, 3.0],
145            [0.1, 0.2, 0.3, 0.4],
146        );
147        assert_eq!(pos.position, [1.0, 2.0, 3.0]);
148        assert_eq!(pos.quaternion, [0.1, 0.2, 0.3, 0.4]);
149    }
150
151    #[test]
152    fn test_encode_size() {
153        let pos = PositionMessage::new(1.0, 2.0, 3.0);
154        let encoded = pos.encode_content().unwrap();
155        assert_eq!(encoded.len(), 28);
156    }
157
158    #[test]
159    fn test_position_roundtrip() {
160        let original = PositionMessage::with_quaternion(
161            [100.5, 200.25, 300.125],
162            [0.1, 0.2, 0.3, 0.9274], // Approximately normalized
163        );
164
165        let encoded = original.encode_content().unwrap();
166        let decoded = PositionMessage::decode_content(&encoded).unwrap();
167
168        assert_eq!(decoded.position, original.position);
169        assert_eq!(decoded.quaternion, original.quaternion);
170    }
171
172    #[test]
173    fn test_big_endian_encoding() {
174        let pos = PositionMessage::new(1.0, 0.0, 0.0);
175        let encoded = pos.encode_content().unwrap();
176
177        // First float (1.0) should be: 0x3F800000 in big-endian
178        assert_eq!(encoded[0], 0x3F);
179        assert_eq!(encoded[1], 0x80);
180        assert_eq!(encoded[2], 0x00);
181        assert_eq!(encoded[3], 0x00);
182    }
183
184    #[test]
185    fn test_decode_invalid_size() {
186        let data = vec![0u8; 20]; // Wrong size
187        let result = PositionMessage::decode_content(&data);
188        assert!(result.is_err());
189    }
190
191    #[test]
192    fn test_get_position() {
193        let pos = PositionMessage::new(10.0, 20.0, 30.0);
194        let (x, y, z) = pos.get_position();
195        assert_eq!((x, y, z), (10.0, 20.0, 30.0));
196    }
197
198    #[test]
199    fn test_get_quaternion() {
200        let pos = PositionMessage::with_quaternion(
201            [0.0, 0.0, 0.0],
202            [0.1, 0.2, 0.3, 0.4],
203        );
204        let (ox, oy, oz, w) = pos.get_quaternion();
205        assert_eq!((ox, oy, oz, w), (0.1, 0.2, 0.3, 0.4));
206    }
207}