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::error::{IgtlError, Result};
8use crate::protocol::message::Message;
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([1.0, 2.0, 3.0], [0.1, 0.2, 0.3, 0.4]);
144        assert_eq!(pos.position, [1.0, 2.0, 3.0]);
145        assert_eq!(pos.quaternion, [0.1, 0.2, 0.3, 0.4]);
146    }
147
148    #[test]
149    fn test_encode_size() {
150        let pos = PositionMessage::new(1.0, 2.0, 3.0);
151        let encoded = pos.encode_content().unwrap();
152        assert_eq!(encoded.len(), 28);
153    }
154
155    #[test]
156    fn test_position_roundtrip() {
157        let original = PositionMessage::with_quaternion(
158            [100.5, 200.25, 300.125],
159            [0.1, 0.2, 0.3, 0.9274], // Approximately normalized
160        );
161
162        let encoded = original.encode_content().unwrap();
163        let decoded = PositionMessage::decode_content(&encoded).unwrap();
164
165        assert_eq!(decoded.position, original.position);
166        assert_eq!(decoded.quaternion, original.quaternion);
167    }
168
169    #[test]
170    fn test_big_endian_encoding() {
171        let pos = PositionMessage::new(1.0, 0.0, 0.0);
172        let encoded = pos.encode_content().unwrap();
173
174        // First float (1.0) should be: 0x3F800000 in big-endian
175        assert_eq!(encoded[0], 0x3F);
176        assert_eq!(encoded[1], 0x80);
177        assert_eq!(encoded[2], 0x00);
178        assert_eq!(encoded[3], 0x00);
179    }
180
181    #[test]
182    fn test_decode_invalid_size() {
183        let data = vec![0u8; 20]; // Wrong size
184        let result = PositionMessage::decode_content(&data);
185        assert!(result.is_err());
186    }
187
188    #[test]
189    fn test_get_position() {
190        let pos = PositionMessage::new(10.0, 20.0, 30.0);
191        let (x, y, z) = pos.get_position();
192        assert_eq!((x, y, z), (10.0, 20.0, 30.0));
193    }
194
195    #[test]
196    fn test_get_quaternion() {
197        let pos = PositionMessage::with_quaternion([0.0, 0.0, 0.0], [0.1, 0.2, 0.3, 0.4]);
198        let (ox, oy, oz, w) = pos.get_quaternion();
199        assert_eq!((ox, oy, oz, w), (0.1, 0.2, 0.3, 0.4));
200    }
201}