use crate::error::{IgtlError, Result};
use crate::protocol::message::Message;
use bytes::{Buf, BufMut};
#[derive(Debug, Clone, PartialEq)]
pub struct PointElement {
pub name: String,
pub group: String,
pub rgba: [u8; 4],
pub position: [f32; 3],
pub diameter: f32,
pub owner: String,
}
impl PointElement {
pub fn new(name: impl Into<String>, group: impl Into<String>, position: [f32; 3]) -> Self {
PointElement {
name: name.into(),
group: group.into(),
rgba: [255, 255, 255, 255], position,
diameter: 0.0,
owner: String::new(),
}
}
pub fn with_color(
name: impl Into<String>,
group: impl Into<String>,
rgba: [u8; 4],
position: [f32; 3],
) -> Self {
PointElement {
name: name.into(),
group: group.into(),
rgba,
position,
diameter: 0.0,
owner: String::new(),
}
}
pub fn with_details(
name: impl Into<String>,
group: impl Into<String>,
rgba: [u8; 4],
position: [f32; 3],
diameter: f32,
owner: impl Into<String>,
) -> Self {
PointElement {
name: name.into(),
group: group.into(),
rgba,
position,
diameter,
owner: owner.into(),
}
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct PointMessage {
pub points: Vec<PointElement>,
}
impl PointMessage {
pub fn new(points: Vec<PointElement>) -> Self {
PointMessage { points }
}
pub fn empty() -> Self {
PointMessage { points: Vec::new() }
}
pub fn add_point(&mut self, point: PointElement) {
self.points.push(point);
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn is_empty(&self) -> bool {
self.points.is_empty()
}
}
impl Message for PointMessage {
fn message_type() -> &'static str {
"POINT"
}
fn encode_content(&self) -> Result<Vec<u8>> {
let mut buf = Vec::with_capacity(self.points.len() * 136);
for point in &self.points {
let mut name_bytes = [0u8; 64];
let name_str = point.name.as_bytes();
let copy_len = name_str.len().min(63);
name_bytes[..copy_len].copy_from_slice(&name_str[..copy_len]);
buf.extend_from_slice(&name_bytes);
let mut group_bytes = [0u8; 32];
let group_str = point.group.as_bytes();
let copy_len = group_str.len().min(31);
group_bytes[..copy_len].copy_from_slice(&group_str[..copy_len]);
buf.extend_from_slice(&group_bytes);
buf.extend_from_slice(&point.rgba);
for &coord in &point.position {
buf.put_f32(coord);
}
buf.put_f32(point.diameter);
let mut owner_bytes = [0u8; 20];
let owner_str = point.owner.as_bytes();
let copy_len = owner_str.len().min(19);
owner_bytes[..copy_len].copy_from_slice(&owner_str[..copy_len]);
buf.extend_from_slice(&owner_bytes);
}
Ok(buf)
}
fn decode_content(mut data: &[u8]) -> Result<Self> {
let mut points = Vec::new();
while data.len() >= 136 {
let name_bytes = &data[..64];
data.advance(64);
let name_len = name_bytes.iter().position(|&b| b == 0).unwrap_or(64);
let name = String::from_utf8(name_bytes[..name_len].to_vec())?;
let group_bytes = &data[..32];
data.advance(32);
let group_len = group_bytes.iter().position(|&b| b == 0).unwrap_or(32);
let group = String::from_utf8(group_bytes[..group_len].to_vec())?;
let rgba = [data.get_u8(), data.get_u8(), data.get_u8(), data.get_u8()];
let position = [data.get_f32(), data.get_f32(), data.get_f32()];
let diameter = data.get_f32();
let owner_bytes = &data[..20];
data.advance(20);
let owner_len = owner_bytes.iter().position(|&b| b == 0).unwrap_or(20);
let owner = String::from_utf8(owner_bytes[..owner_len].to_vec())?;
points.push(PointElement {
name,
group,
rgba,
position,
diameter,
owner,
});
}
if !data.is_empty() {
return Err(IgtlError::InvalidSize {
expected: 0,
actual: data.len(),
});
}
Ok(PointMessage { points })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_message_type() {
assert_eq!(PointMessage::message_type(), "POINT");
}
#[test]
fn test_empty() {
let msg = PointMessage::empty();
assert!(msg.is_empty());
assert_eq!(msg.len(), 0);
}
#[test]
fn test_new_point() {
let point = PointElement::new("Fiducial1", "Landmark", [10.0, 20.0, 30.0]);
assert_eq!(point.name, "Fiducial1");
assert_eq!(point.group, "Landmark");
assert_eq!(point.position, [10.0, 20.0, 30.0]);
assert_eq!(point.rgba, [255, 255, 255, 255]);
}
#[test]
fn test_point_with_color() {
let point =
PointElement::with_color("Point1", "Fiducial", [255, 0, 0, 255], [1.0, 2.0, 3.0]);
assert_eq!(point.rgba, [255, 0, 0, 255]);
}
#[test]
fn test_add_point() {
let mut msg = PointMessage::empty();
msg.add_point(PointElement::new("P1", "Landmark", [0.0, 0.0, 0.0]));
assert_eq!(msg.len(), 1);
}
#[test]
fn test_encode_single_point() {
let point = PointElement::new("Test", "Fiducial", [1.0, 2.0, 3.0]);
let msg = PointMessage::new(vec![point]);
let encoded = msg.encode_content().unwrap();
assert_eq!(encoded.len(), 136);
}
#[test]
fn test_roundtrip_single() {
let original = PointMessage::new(vec![PointElement::with_details(
"Fiducial1",
"Landmark",
[255, 128, 64, 255],
[100.5, 200.5, 300.5],
5.0,
"Image1",
)]);
let encoded = original.encode_content().unwrap();
let decoded = PointMessage::decode_content(&encoded).unwrap();
assert_eq!(decoded.points.len(), 1);
assert_eq!(decoded.points[0].name, "Fiducial1");
assert_eq!(decoded.points[0].group, "Landmark");
assert_eq!(decoded.points[0].rgba, [255, 128, 64, 255]);
assert_eq!(decoded.points[0].position, [100.5, 200.5, 300.5]);
assert_eq!(decoded.points[0].diameter, 5.0);
assert_eq!(decoded.points[0].owner, "Image1");
}
#[test]
fn test_roundtrip_multiple() {
let original = PointMessage::new(vec![
PointElement::new("P1", "Landmark", [1.0, 2.0, 3.0]),
PointElement::new("P2", "Fiducial", [4.0, 5.0, 6.0]),
PointElement::new("P3", "Target", [7.0, 8.0, 9.0]),
]);
let encoded = original.encode_content().unwrap();
let decoded = PointMessage::decode_content(&encoded).unwrap();
assert_eq!(decoded.points.len(), 3);
assert_eq!(decoded.points[0].name, "P1");
assert_eq!(decoded.points[1].name, "P2");
assert_eq!(decoded.points[2].name, "P3");
}
#[test]
fn test_name_truncation() {
let long_name = "A".repeat(100);
let point = PointElement::new(&long_name, "Group", [0.0, 0.0, 0.0]);
let msg = PointMessage::new(vec![point]);
let encoded = msg.encode_content().unwrap();
let decoded = PointMessage::decode_content(&encoded).unwrap();
assert!(decoded.points[0].name.len() <= 63);
}
#[test]
fn test_empty_message() {
let msg = PointMessage::empty();
let encoded = msg.encode_content().unwrap();
let decoded = PointMessage::decode_content(&encoded).unwrap();
assert_eq!(decoded.points.len(), 0);
assert_eq!(encoded.len(), 0);
}
#[test]
fn test_decode_invalid_size() {
let data = vec![0u8; 135]; let result = PointMessage::decode_content(&data);
assert!(result.is_err());
}
#[test]
fn test_color_values() {
let point =
PointElement::with_color("ColorTest", "Test", [128, 64, 32, 200], [0.0, 0.0, 0.0]);
let msg = PointMessage::new(vec![point]);
let encoded = msg.encode_content().unwrap();
let decoded = PointMessage::decode_content(&encoded).unwrap();
assert_eq!(decoded.points[0].rgba, [128, 64, 32, 200]);
}
}