rust_3d/
point_cloud_3d.rs

1/*
2Copyright 2016 Martin Buck
3
4Permission is hereby granted, free of charge, to any person obtaining a copy
5of this software and associated documentation files (the "Software"),
6to deal in the Software without restriction, including without limitation the
7rights to use, copy, modify, merge, publish, distribute, sublicense,
8and/or sell copies of the Software, and to permit persons to whom the Software
9is furnished to do so, subject to the following conditions:
10
11The above copyright notice and this permission notice shall
12be included all copies or substantial portions of the Software.
13
14THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
17IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
19TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE
20OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21*/
22
23//! PointCloud3D, a collection of positions within 3D space
24
25use std::{
26    fmt,
27    ops::{Index, IndexMut},
28};
29
30use crate::*;
31
32//------------------------------------------------------------------------------
33
34#[derive(Debug, PartialEq, PartialOrd, Ord, Eq, Clone, Hash)]
35/// PointCloud3D, a collection of positions within 3D space
36pub struct PointCloud3D<P>
37where
38    P: Is3D,
39{
40    pub data: Vec<P>,
41}
42
43impl<P> PointCloud3D<P>
44where
45    P: Is3D,
46{
47    /// Creates a new, empty point cloud
48    pub fn new() -> PointCloud3D<P> {
49        PointCloud3D { data: Vec::new() }
50    }
51    /// Creates a new, empty point cloud with capacity
52    pub fn with_capacity(n: usize) -> PointCloud3D<P> {
53        PointCloud3D {
54            data: Vec::with_capacity(n),
55        }
56    }
57    /// Serializes the point cloud
58    pub fn to_str(&self) -> String {
59        let mut result = String::new();
60        for p in &self.data {
61            result = result + &p.to_str() + "\n";
62        }
63        result
64    }
65    /// Applies a function to each position
66    pub fn for_each_point<F>(&mut self, mut f: F)
67    where
68        F: FnMut(&mut P),
69    {
70        for p in &mut self.data {
71            f(&mut *p);
72        }
73    }
74    /// Reserves number of vertices
75    pub fn reserve_vertices(&mut self, n: usize) {
76        self.data.reserve(n)
77    }
78}
79
80impl<P> PointCloud3D<P>
81where
82    P: IsBuildable3D + Clone,
83{
84    /// Creates a new point cloud from an input string
85    pub fn parse(text: &str) -> Result<PointCloud3D<P>> {
86        let lines = text.split("\n").skip_empty_string();
87
88        let mut pc = PointCloud3D::new();
89        for line in lines {
90            P::parse(line).map(|p| pc.push(p))?;
91        }
92        if pc.len() == 0 {
93            return Err(ErrorKind::ParseError);
94        }
95        Ok(pc)
96    }
97    /// Appends all elements of an IsRandomAccessible
98    pub fn append_ra<RA>(&mut self, ra: &RA)
99    where
100        RA: IsRandomAccessible<P>,
101    {
102        let n = ra.len();
103        self.data.reserve(n);
104
105        for i in 0..n {
106            self.data.push(ra[i].clone());
107        }
108    }
109}
110
111//------------------------------------------------------------------------------
112
113impl<P> IsDataContainer<P> for PointCloud3D<P>
114where
115    P: IsBuildable3D + Clone,
116{
117    fn reserve_d(&mut self, n: usize) {
118        self.data.reserve(n);
119    }
120
121    fn len_d(&self) -> usize {
122        self.data.len()
123    }
124
125    fn push_d(&mut self, p: P) {
126        self.push(p)
127    }
128
129    fn get_d(&self, index: usize) -> P {
130        self.data[index].clone()
131    }
132
133    fn set_d(&mut self, index: usize, p: P) {
134        self.data[index] = p
135    }
136}
137
138impl<P> Index<usize> for PointCloud3D<P>
139where
140    P: Is3D,
141{
142    type Output = P;
143    fn index(&self, i: usize) -> &P {
144        &self.data[i]
145    }
146}
147
148impl<P> IndexMut<usize> for PointCloud3D<P>
149where
150    P: Is3D,
151{
152    fn index_mut(&mut self, i: usize) -> &mut P {
153        &mut self.data[i]
154    }
155}
156
157impl<P> IsRandomAccessible<P> for PointCloud3D<P>
158where
159    P: Is3D,
160{
161    fn len(&self) -> usize {
162        self.data.len()
163    }
164}
165
166impl<P> IsRandomInsertible<P> for PointCloud3D<P>
167where
168    P: Is3D,
169{
170    fn insert(&mut self, index: usize, point: P) -> Result<()> {
171        if index > self.len() {
172            Err(ErrorKind::IncorrectVertexID)
173        } else {
174            self.data.insert(index, point);
175            Ok(())
176        }
177    }
178}
179
180impl<P> IsPushable<P> for PointCloud3D<P>
181where
182    P: Is3D,
183{
184    fn push(&mut self, point: P) {
185        self.data.push(point)
186    }
187    fn reserve(&mut self, n: usize) {
188        self.data.reserve(n)
189    }
190}
191
192impl<P> IsMovable3D for PointCloud3D<P>
193where
194    P: Is3D + IsMovable3D,
195{
196    fn move_by(&mut self, x: f64, y: f64, z: f64) {
197        for p in &mut self.data {
198            p.move_by(x, y, z);
199        }
200    }
201}
202
203impl<P> HasBoundingBox3DMaybe for PointCloud3D<P>
204where
205    P: Is3D,
206{
207    fn bounding_box_maybe(&self) -> Result<BoundingBox3D> {
208        BoundingBox3D::from_iterator(&self.data)
209    }
210}
211
212impl<P> HasCenterOfGravity3D for PointCloud3D<P>
213where
214    P: Is3D,
215{
216    fn center_of_gravity(&self) -> Result<Point3D> {
217        let size = self.len();
218
219        if size < 1 {
220            return Err(ErrorKind::TooFewPoints);
221        }
222
223        let sizef = size as f64;
224
225        let mut sumx: f64 = 0.0;
226        let mut sumy: f64 = 0.0;
227        let mut sumz: f64 = 0.0;
228
229        for p in &self.data {
230            sumx += p.x();
231            sumy += p.y();
232            sumz += p.z();
233        }
234
235        Ok(Point3D {
236            x: sumx / sizef,
237            y: sumy / sizef,
238            z: sumz / sizef,
239        })
240    }
241}
242
243impl<P> HasLength for PointCloud3D<P>
244where
245    P: Is3D,
246{
247    fn length(&self) -> f64 {
248        let mut length: f64 = 0.0;
249        if self.data.len() < 2 {
250            return length;
251        }
252
253        for i in 1..self.data.len() {
254            length += dist_3d(&self.data[i], &self.data[i - 1]);
255        }
256        length
257    }
258}
259
260impl<P> IsViewBuildable for PointCloud3D<P>
261where
262    P: Is3D + Clone,
263{
264    fn apply_view(&mut self, view: &View) -> Result<()> {
265        self.data.apply_view(view)?;
266        Ok(())
267    }
268
269    fn from_view(&self, view: &View) -> Result<Self> {
270        let mut cloned = self.clone();
271        cloned.apply_view(view)?;
272        Ok(cloned)
273    }
274}
275
276impl<P> IsSortableND for PointCloud3D<P>
277where
278    P: Is3D,
279{
280    fn n_dimensions() -> usize {
281        3
282    }
283
284    fn sort_dim(&mut self, dimension: usize) -> Result<()> {
285        match dimension {
286            0 => {
287                self.sort_x();
288                Ok(())
289            }
290            1 => {
291                self.sort_y();
292                Ok(())
293            }
294            2 => {
295                self.sort_z();
296                Ok(())
297            }
298            _ => Err(ErrorKind::IncorrectDimension),
299        }
300    }
301}
302
303impl<P> IsSortable3D for PointCloud3D<P>
304where
305    P: Is3D,
306{
307    fn sort_x(&mut self) {
308        sort_vec_3d_x(&mut self.data);
309    }
310
311    fn sort_y(&mut self) {
312        sort_vec_3d_y(&mut self.data);
313    }
314
315    fn sort_z(&mut self) {
316        sort_vec_3d_z(&mut self.data);
317    }
318}
319
320impl<P> IsMergeable for PointCloud3D<P>
321where
322    P: Is3D + Clone,
323{
324    fn consume(&mut self, other: Self) {
325        for p in other.data {
326            self.data.push(p.clone());
327        }
328    }
329
330    fn combine(&self, other: &Self) -> Self {
331        let mut result = self.clone();
332        result.consume(other.clone());
333        result
334    }
335}
336
337impl<P> IsScalable for PointCloud3D<P>
338where
339    P: IsEditable3D,
340{
341    fn scale(&mut self, factor: Positive) {
342        if let Ok(bb) = self.bounding_box_maybe() {
343            let c = bb.center_bb();
344            for p in &mut self.data {
345                p.increase_distance_to_by(&c, factor);
346            }
347        }
348    }
349}
350
351impl<P> IsMatrix4Transformable for PointCloud3D<P>
352where
353    P: Is3D + IsMatrix4Transformable + Clone,
354{
355    fn transformed(&self, m: &Matrix4) -> Self {
356        let mut new = self.clone();
357        new.transform(m);
358        new
359    }
360
361    fn transform(&mut self, m: &Matrix4) {
362        for p in &mut self.data {
363            p.transform(m);
364        }
365    }
366}
367
368impl<P> Default for PointCloud3D<P>
369where
370    //https://github.com/rust-lang/rust/issues/26925
371    P: Is3D,
372{
373    fn default() -> Self {
374        let data = Vec::new();
375        PointCloud3D { data }
376    }
377}
378
379impl<P> fmt::Display for PointCloud3D<P>
380where
381    P: Is3D + fmt::Display,
382{
383    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
384        for p in &self.data {
385            p.fmt(f)?;
386            f.write_str("\n")?;
387        }
388        Ok(())
389    }
390}
391
392impl<P> Into<Vec<P>> for PointCloud3D<P>
393where
394    P: Is3D,
395{
396    fn into(self) -> Vec<P> {
397        self.data
398    }
399}
400
401impl<P> From<Vec<P>> for PointCloud3D<P>
402where
403    P: Is3D,
404{
405    fn from(data: Vec<P>) -> Self {
406        Self { data }
407    }
408}