session_rust/
pointcloud.rs

1use crate::{Color, Point, Vector, Xform};
2use serde::{Deserialize, Deserializer, Serialize, Serializer};
3use std::fmt;
4use std::ops::{Add, AddAssign, Sub, SubAssign};
5use uuid::Uuid;
6
7#[derive(Debug, Clone)]
8pub struct PointCloud {
9    pub guid: String,
10    pub name: String,
11    pub points: Vec<Point>,
12    pub normals: Vec<Vector>,
13    pub colors: Vec<Color>,
14    pub xform: Xform,
15}
16
17impl Default for PointCloud {
18    fn default() -> Self {
19        Self {
20            guid: Uuid::new_v4().to_string(),
21            name: "my_pointcloud".to_string(),
22            points: Vec::new(),
23            normals: Vec::new(),
24            colors: Vec::new(),
25            xform: Xform::identity(),
26        }
27    }
28}
29
30impl PointCloud {
31    pub fn new(points: Vec<Point>, normals: Vec<Vector>, colors: Vec<Color>) -> Self {
32        Self {
33            points,
34            normals,
35            colors,
36            ..Default::default()
37        }
38    }
39
40    pub fn len(&self) -> usize {
41        self.points.len()
42    }
43
44    pub fn is_empty(&self) -> bool {
45        self.points.is_empty()
46    }
47
48    ///////////////////////////////////////////////////////////////////////////////////////////
49    // JSON
50    ///////////////////////////////////////////////////////////////////////////////////////////
51
52    pub fn jsondump(&self) -> Result<String, Box<dyn std::error::Error>> {
53        let mut buf = Vec::new();
54        let formatter = serde_json::ser::PrettyFormatter::with_indent(b"    ");
55        let mut ser = serde_json::Serializer::with_formatter(&mut buf, formatter);
56        serde::Serialize::serialize(self, &mut ser)?;
57        Ok(String::from_utf8(buf)?)
58    }
59
60    pub fn jsonload(json_str: &str) -> Result<Self, Box<dyn std::error::Error>> {
61        Ok(serde_json::from_str(json_str)?)
62    }
63
64    pub fn to_json(&self, filepath: &str) -> Result<(), Box<dyn std::error::Error>> {
65        let json_str = self.jsondump()?;
66        std::fs::write(filepath, json_str)?;
67        Ok(())
68    }
69
70    pub fn from_json(filepath: &str) -> Result<Self, Box<dyn std::error::Error>> {
71        let json_str = std::fs::read_to_string(filepath)?;
72        Self::jsonload(&json_str)
73    }
74}
75
76///////////////////////////////////////////////////////////////////////////////////////////
77// No-copy Operators
78///////////////////////////////////////////////////////////////////////////////////////////
79
80impl AddAssign<Vector> for PointCloud {
81    fn add_assign(&mut self, other: Vector) {
82        for p in &mut self.points {
83            *p += other.clone();
84        }
85    }
86}
87
88impl SubAssign<Vector> for PointCloud {
89    fn sub_assign(&mut self, other: Vector) {
90        for p in &mut self.points {
91            *p -= other.clone();
92        }
93    }
94}
95
96///////////////////////////////////////////////////////////////////////////////////////////
97// Copy Operators
98///////////////////////////////////////////////////////////////////////////////////////////
99
100impl Add<Vector> for PointCloud {
101    type Output = PointCloud;
102
103    fn add(self, other: Vector) -> PointCloud {
104        let mut result = self.clone();
105        result += other;
106        result
107    }
108}
109
110impl Sub<Vector> for PointCloud {
111    type Output = PointCloud;
112
113    fn sub(self, other: Vector) -> PointCloud {
114        let mut result = self.clone();
115        result -= other;
116        result
117    }
118}
119
120impl fmt::Display for PointCloud {
121    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122        write!(
123            f,
124            "PointCloud(points={}, normals={}, colors={}, guid={}, name={})",
125            self.points.len(),
126            self.normals.len(),
127            self.colors.len(),
128            self.guid,
129            self.name
130        )
131    }
132}
133
134///////////////////////////////////////////////////////////////////////////////////////////
135// Custom Serialization - Flat arrays for efficiency
136///////////////////////////////////////////////////////////////////////////////////////////
137
138impl Serialize for PointCloud {
139    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
140    where
141        S: Serializer,
142    {
143        use serde::ser::SerializeStruct;
144        let mut state = serializer.serialize_struct("PointCloud", 6)?;
145
146        state.serialize_field("type", "PointCloud")?;
147        state.serialize_field("guid", &self.guid)?;
148        state.serialize_field("name", &self.name)?;
149
150        // Flatten points to [x, y, z, x, y, z, ...]
151        let points_flat: Vec<f32> = self
152            .points
153            .iter()
154            .flat_map(|p| vec![p.x(), p.y(), p.z()])
155            .collect();
156        state.serialize_field("points", &points_flat)?;
157
158        // Flatten normals to [x, y, z, x, y, z, ...]
159        let normals_flat: Vec<f32> = self
160            .normals
161            .iter()
162            .flat_map(|n| vec![n.x(), n.y(), n.z()])
163            .collect();
164        state.serialize_field("normals", &normals_flat)?;
165
166        // Flatten colors to [r, g, b, r, g, b, ...] (no alpha)
167        let colors_flat: Vec<u8> = self
168            .colors
169            .iter()
170            .flat_map(|c| vec![c.r, c.g, c.b])
171            .collect();
172        state.serialize_field("colors", &colors_flat)?;
173
174        state.serialize_field("xform", &self.xform)?;
175
176        state.end()
177    }
178}
179
180impl<'de> Deserialize<'de> for PointCloud {
181    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
182    where
183        D: Deserializer<'de>,
184    {
185        use serde::de::{self, MapAccess, Visitor};
186
187        #[derive(Deserialize)]
188        #[serde(field_identifier, rename_all = "lowercase")]
189        enum Field {
190            Type,
191            Guid,
192            Name,
193            Points,
194            Normals,
195            Colors,
196            Xform,
197        }
198
199        struct PointCloudVisitor;
200
201        impl<'de> Visitor<'de> for PointCloudVisitor {
202            type Value = PointCloud;
203
204            fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
205                formatter.write_str("struct PointCloud")
206            }
207
208            fn visit_map<V>(self, mut map: V) -> Result<PointCloud, V::Error>
209            where
210                V: MapAccess<'de>,
211            {
212                let mut guid = None;
213                let mut name = None;
214                let mut points_flat: Option<Vec<f32>> = None;
215                let mut normals_flat: Option<Vec<f32>> = None;
216                let mut colors_flat: Option<Vec<u8>> = None;
217                let mut xform = None;
218
219                while let Some(key) = map.next_key()? {
220                    match key {
221                        Field::Type => {
222                            let _: String = map.next_value()?;
223                        }
224                        Field::Guid => {
225                            guid = Some(map.next_value()?);
226                        }
227                        Field::Name => {
228                            name = Some(map.next_value()?);
229                        }
230                        Field::Points => {
231                            points_flat = Some(map.next_value()?);
232                        }
233                        Field::Normals => {
234                            normals_flat = Some(map.next_value()?);
235                        }
236                        Field::Colors => {
237                            colors_flat = Some(map.next_value()?);
238                        }
239                        Field::Xform => {
240                            xform = Some(map.next_value()?);
241                        }
242                    }
243                }
244
245                let guid = guid.ok_or_else(|| de::Error::missing_field("guid"))?;
246                let name = name.ok_or_else(|| de::Error::missing_field("name"))?;
247                let points_flat = points_flat.ok_or_else(|| de::Error::missing_field("points"))?;
248                let normals_flat =
249                    normals_flat.ok_or_else(|| de::Error::missing_field("normals"))?;
250                let colors_flat = colors_flat.ok_or_else(|| de::Error::missing_field("colors"))?;
251                let xform = xform.ok_or_else(|| de::Error::missing_field("xform"))?;
252
253                // Reconstruct points from flat array
254                let points: Vec<Point> = points_flat
255                    .chunks(3)
256                    .map(|chunk| Point::new(chunk[0], chunk[1], chunk[2]))
257                    .collect();
258
259                // Reconstruct normals from flat array
260                let normals: Vec<Vector> = normals_flat
261                    .chunks(3)
262                    .map(|chunk| Vector::new(chunk[0], chunk[1], chunk[2]))
263                    .collect();
264
265                // Reconstruct colors from flat array (RGB only, alpha always 255)
266                let colors: Vec<Color> = colors_flat
267                    .chunks(3)
268                    .map(|chunk| Color::new(chunk[0], chunk[1], chunk[2], 255))
269                    .collect();
270
271                Ok(PointCloud {
272                    guid,
273                    name,
274                    points,
275                    normals,
276                    colors,
277                    xform,
278                })
279            }
280        }
281
282        const FIELDS: &[&str] = &[
283            "type", "guid", "name", "points", "normals", "colors", "xform",
284        ];
285        deserializer.deserialize_struct("PointCloud", FIELDS, PointCloudVisitor)
286    }
287}
288
289#[cfg(test)]
290#[path = "pointcloud_test.rs"]
291mod pointcloud_test;