pasture_core/layout/conversion/
buffer_conversion.rs

1use std::ops::Range;
2
3use crate::{
4    containers::{
5        BorrowedBuffer, BorrowedMutBuffer, ColumnarBuffer, ColumnarBufferMut, InterleavedBuffer,
6        InterleavedBufferMut, MakeBufferFromLayout, OwningBuffer,
7    },
8    layout::{PointAttributeDefinition, PointAttributeMember, PointLayout, PrimitiveType},
9};
10
11use super::{get_generic_converter, AttributeConversionFn};
12
13/// Function that transform a single point attribute in its raw, untyped form
14type AttributeTransformFn = Box<dyn Fn(&mut [u8])>;
15
16/// Converts the given transform function into an untyped version that works on raw attribute memory
17fn to_untyped_transform_fn<T: PrimitiveType, F: Fn(T) -> T + 'static>(
18    transform_fn: F,
19) -> AttributeTransformFn {
20    let untyped_transform_fn = move |attribute_memory: &mut [u8]| {
21        let attribute_ptr_typed = attribute_memory.as_mut_ptr() as *mut T;
22        // Is safe as long as the caller ensures that `attribute_memory` contains valid memory for `T`
23        // The `BufferLayoutConverter` ensures this because we perform a attribute data type check above
24        unsafe {
25            let attribute_value = attribute_ptr_typed.read_unaligned();
26            let transformed_value = transform_fn(attribute_value);
27            attribute_ptr_typed.write_unaligned(transformed_value);
28        }
29    };
30    Box::new(untyped_transform_fn)
31}
32
33pub struct Transformation {
34    func: AttributeTransformFn,
35    apply_to_source_attribute: bool,
36}
37
38/// Mapping between a source `PointAttributeMember` and a target `PointAttributeMember`. This type stores
39/// all the necessary data to convert a single source attribute value into the target attribute. It supports
40/// type conversion and transformations
41pub struct AttributeMapping<'a> {
42    target_attribute: &'a PointAttributeMember,
43    source_attribute: &'a PointAttributeMember,
44    converter: Option<AttributeConversionFn>,
45    transformation: Option<Transformation>,
46}
47
48impl<'a> AttributeMapping<'a> {
49    /// Returns the maximum number of bytes required to convert the data for this mapping
50    pub(crate) fn required_buffer_size(&self) -> usize {
51        self.source_attribute
52            .size()
53            .max(self.target_attribute.size()) as usize
54    }
55}
56
57/// A converter that can convert a point buffer from one `PointLayout` into another. This works by defining *mappings*
58/// between attributes from the source buffer and attributes in the target buffer. All mappings together define the
59/// way the source `PointLayout` transforms into the target `PointLayout`. Here is an example:
60///
61/// Source layout: `[POSITION_3D(Vector3<i32>), CLASSIFICATION(u8), COLOR_RGB(Vector3<u16>)]`<br>
62/// Target layout: `[COLOR_RGB(Vector3<u8>), POSITION_3D(Vector3<f64>)]`
63///
64/// Mappings: <br>
65/// `POSITION_3D(Vector3<i32>)` to `POSITION_3D(Vector3<f64>)` <br>
66/// `COLOR_RGB(Vector3<u16>)`   to `COLOR_RGB(Vector3<u8>)`
67///
68/// This means that every position from the source buffer is converted into a position in the target buffer, with
69/// a type conversion from `Vector3<i32>` to `Vector3<f64>`, and every color from the source buffer is converted
70/// into a color in the target buffer, also with a type conversion from `Vector3<u16>` to `Vector3<u8>`. Type conversions
71/// follow the rules of the attribute converters as explained in [`get_converter_for_attributes`](`crate::layout::conversion::get_converter_for_attributes`).
72///
73/// ## Default mappings
74///
75/// By default, if [`BufferLayoutConverter::for_layouts`] is called, all attributes in the source layout are mapped to
76/// attributes in the target layout that have the same name. All other attributes are ignored. If the attributes have
77/// different datatypes, a default type converter is used. If there are attributes in the target layout that do not have
78/// a matching attribute in the source layout, this will raise an error, unless [`BufferLayoutConverter::for_layouts_with_default]`
79/// is called, in which case the target attribute(s) will be filled with default values.
80///
81/// ## Custom mappings
82///
83/// It is also possible to define custom mappings, which allow more flexibility than the default mappings. In particular:
84/// - Multiple mappings may refer the same source attribute (but there can only be at most one mapping per target attribute)
85/// - Custom mappings support attribute transformations using an arbitrary transformation function `T -> T`
86///
87/// Overriding existing mappings can be done by calling [`set_custom_mapping`] and [`set_custom_mapping_with_transformation`].
88/// Here are some examples for what can be done with custom mappings:
89/// - Extract bitfield attributes into multiple distinct attributes by creating multiple mappings from the bitfield attribute
90///   to the target attributes with different transformation functions that extract the relevant bits
91/// - Offsetting and scaling values during transformation (such as going from local coordinates to world-space coordinates in
92///   an LAS file)
93///
94/// ## On performance
95///
96/// Buffer layout conversion works with buffers with arbitrary memory layouts, but since the mappings are defined per attribute,
97/// the best possible performance can be achieved if both the source and target buffer are in columnar memory layout.
98pub struct BufferLayoutConverter<'a> {
99    from_layout: &'a PointLayout,
100    to_layout: &'a PointLayout,
101    mappings: Vec<AttributeMapping<'a>>,
102}
103
104impl<'a> BufferLayoutConverter<'a> {
105    /// Creates a new `BufferLayoutConverter` from `from_layout` into `to_layout`. This generates default mappings for each
106    /// attribute in `to_layout` following the rules of [`crate::layout::conversion::get_converter_for_attributes`]
107    ///
108    /// # Panics
109    ///
110    /// If any `PointAttributeMember` in `to_layout` is not present (by name) in `from_layout`. If you want to allow missing
111    /// attributes in `from_layout` and fill their values with default values, use [`Self::for_layouts_with_default`] instead!
112    pub fn for_layouts(from_layout: &'a PointLayout, to_layout: &'a PointLayout) -> Self {
113        let default_mappings = to_layout.attributes().map(|to_attribute| {
114            let from_attribute = from_layout.get_attribute_by_name(to_attribute.attribute_definition().name()).expect("Attribute not found in `from_layout`! When calling `BufferLayoutConverter::for_layouts`, the source PointLayout must contain all attributes from the target PointLayout. If you want to use default values for attributes that are not present in the source layout, use `BufferLayoutConverter::for_layouts_with_default` instead!");
115            Self::make_default_mapping(from_attribute, to_attribute)
116        }).collect();
117        Self {
118            from_layout,
119            to_layout,
120            mappings: default_mappings,
121        }
122    }
123
124    /// Like [`Self::for_layouts`], but if an attribute from `to_layout` is not present in `from_layout`, the conversion still
125    /// happens and will fill the target buffer with default values for this attribute instead!
126    pub fn for_layouts_with_default(
127        from_layout: &'a PointLayout,
128        to_layout: &'a PointLayout,
129    ) -> Self {
130        let default_mappings = to_layout
131            .attributes()
132            .filter_map(|to_attribute| {
133                from_layout
134                    .get_attribute_by_name(to_attribute.attribute_definition().name())
135                    .map(|from_attribute| Self::make_default_mapping(from_attribute, to_attribute))
136            })
137            .collect();
138        Self {
139            from_layout,
140            to_layout,
141            mappings: default_mappings,
142        }
143    }
144
145    /// Sets a custom mapping from `from_attribute` to `to_attribute`. This overrides the default mapping function for
146    /// `to_attribute`. It allows mapping attributes with different names to each other and in particular allows the
147    /// conversion to be surjective: Multiple attributes in `to_attribute` can be converted from the same attribute in
148    /// `from_attribute`. An example where this is useful are bit-flag attributes, such as in the LAS file format, which
149    /// semantically represent multiple attributes, but are represented as a single attribute in pasture.
150    ///
151    ///
152    /// # Panics
153    ///
154    /// If `from_attribute` is not part of the source `PointLayout`.
155    /// If `to_attribute` is not part of the target `PointLayout`.
156    pub fn set_custom_mapping(
157        &mut self,
158        from_attribute: &PointAttributeDefinition,
159        to_attribute: &PointAttributeDefinition,
160    ) {
161        let from_attribute_member = self
162            .from_layout
163            .get_attribute(from_attribute)
164            .expect("from_attribute not found in source PointLayout");
165        let to_attribute_member = self
166            .to_layout
167            .get_attribute(to_attribute)
168            .expect("to_attribute not found in target PointLayout");
169
170        if let Some(previous_mapping) = self
171            .mappings
172            .iter_mut()
173            .find(|mapping| mapping.target_attribute.attribute_definition() == to_attribute)
174        {
175            *previous_mapping =
176                Self::make_default_mapping(from_attribute_member, to_attribute_member);
177        } else {
178            self.mappings.push(Self::make_default_mapping(
179                from_attribute_member,
180                to_attribute_member,
181            ));
182        }
183    }
184
185    /// Like [`Self::set_custom_mapping`], but transforms the attribute value using the `transform_fn`. If
186    /// `apply_to_source_attribute` is `true`, the transformation will be applied to the source attribute value
187    /// prior to any potential conversion, otherwise it is applied after conversion
188    ///
189    /// # Panics
190    ///
191    /// If `from_attribute` is not part of the source `PointLayout`.
192    /// If `to_attribute` is not part of the target `PointLayout`.
193    /// If `T::data_type()` does not match `to_attribute.datatype()`.
194    pub fn set_custom_mapping_with_transformation<T: PrimitiveType, F: Fn(T) -> T + 'static>(
195        &mut self,
196        from_attribute: &PointAttributeDefinition,
197        to_attribute: &PointAttributeDefinition,
198        transform_fn: F,
199        apply_to_source_attribute: bool,
200    ) {
201        let from_attribute_member = self
202            .from_layout
203            .get_attribute(from_attribute)
204            .expect("from_attribute not found in source PointLayout");
205        let to_attribute_member = self
206            .to_layout
207            .get_attribute(to_attribute)
208            .expect("to_attribute not found in target PointLayout");
209        if apply_to_source_attribute {
210            assert_eq!(T::data_type(), from_attribute_member.datatype());
211        } else {
212            assert_eq!(T::data_type(), to_attribute_member.datatype());
213        }
214
215        if let Some(previous_mapping) = self
216            .mappings
217            .iter_mut()
218            .find(|mapping| mapping.target_attribute.attribute_definition() == to_attribute)
219        {
220            *previous_mapping = Self::make_transformed_mapping(
221                from_attribute_member,
222                to_attribute_member,
223                transform_fn,
224                apply_to_source_attribute,
225            );
226        } else {
227            self.mappings.push(Self::make_transformed_mapping(
228                from_attribute_member,
229                to_attribute_member,
230                transform_fn,
231                apply_to_source_attribute,
232            ));
233        }
234    }
235
236    /// Convert the `source_buffer` into the target `PointLayout` and return its data as a new buffer of
237    /// type `OutBuffer`
238    ///
239    /// # Panics
240    ///
241    /// If `source_buffer.point_layout()` does not match the source `PointLayout` used to construct this `BufferLayoutConverter`
242    pub fn convert<
243        'b,
244        'c,
245        'd,
246        OutBuffer: OwningBuffer<'c> + MakeBufferFromLayout<'c> + 'c,
247        InBuffer: BorrowedBuffer<'b>,
248    >(
249        &self,
250        source_buffer: &'d InBuffer,
251    ) -> OutBuffer
252    where
253        'b: 'd,
254    {
255        let mut target_buffer = OutBuffer::new_from_layout(self.to_layout.clone());
256        target_buffer.resize(source_buffer.len());
257        self.convert_into(source_buffer, &mut target_buffer);
258        target_buffer
259    }
260
261    /// Like [`convert`], but converts into an existing buffer instead of allocating a new buffer
262    ///
263    /// # Panics
264    ///
265    /// If `source_buffer.point_layout()` does not match the source `PointLayout` used to construct this `BufferLayoutConverter`
266    /// If `target_buffer.point_layout()` does not match the target `PointLayout` used to construct this `BufferLayoutConverter`
267    /// If `target_buffer.len()` is not equal to `source_buffer.len()`
268    pub fn convert_into<'b, 'c, 'd, 'e>(
269        &self,
270        source_buffer: &'c impl BorrowedBuffer<'b>,
271        target_buffer: &'e mut impl BorrowedMutBuffer<'d>,
272    ) where
273        'b: 'c,
274        'd: 'e,
275    {
276        let source_range = 0..source_buffer.len();
277        self.convert_into_range(
278            source_buffer,
279            source_range.clone(),
280            target_buffer,
281            source_range,
282        );
283    }
284
285    /// Like [`convert_into`], but converts the points from `source_range` in `source_buffer` into the `target_range` in `target_buffer`
286    ///
287    /// # Panics
288    ///
289    /// If `source_buffer.point_layout()` does not match the source `PointLayout` used to construct this `BufferLayoutConverter`
290    /// If `target_buffer.point_layout()` does not match the target `PointLayout` used to construct this `BufferLayoutConverter`
291    /// If `target_buffer.len()` is less than `source_buffer.len()`
292    pub fn convert_into_range<'b, 'c, 'd, 'e>(
293        &self,
294        source_buffer: &'c impl BorrowedBuffer<'b>,
295        source_range: Range<usize>,
296        target_buffer: &'e mut impl BorrowedMutBuffer<'d>,
297        target_range: Range<usize>,
298    ) where
299        'b: 'c,
300        'd: 'e,
301    {
302        assert_eq!(source_buffer.point_layout(), self.from_layout);
303        assert_eq!(target_buffer.point_layout(), self.to_layout);
304        assert!(source_range.len() == target_range.len());
305        assert!(source_range.end <= source_buffer.len());
306        assert!(target_range.end <= target_buffer.len());
307
308        let max_attribute_size = self
309            .mappings
310            .iter()
311            .map(|mapping| mapping.required_buffer_size())
312            .max();
313        if let Some(max_attribute_size) = max_attribute_size {
314            // Currently, we only support conversions between interleaved and/or columnar buffers. Buffers that implement
315            // neither `InterleavedBuffer` nor `ColumnarBuffer` would be possible, but more inefficient to implement
316            match (source_buffer.as_columnar(), target_buffer.as_columnar_mut()) {
317                (Some(source_buffer), Some(target_buffer)) => {
318                    self.convert_columnar_to_columnar(
319                        source_buffer,
320                        source_range,
321                        target_buffer,
322                        target_range,
323                    );
324                }
325                (Some(source_buffer), None) => {
326                    self.convert_columnar_to_interleaved(
327                        source_buffer,
328                        source_range,
329                        target_buffer.as_interleaved_mut().expect(
330                            "Target buffer must either be an interleaved or columnar buffer",
331                        ),
332                        target_range,
333                    );
334                }
335                (None, Some(target_buffer)) => {
336                    self.convert_interleaved_to_columnar(
337                        source_buffer.as_interleaved().expect(
338                            "Source buffer must either be an interleaved or columnar buffer",
339                        ),
340                        source_range,
341                        target_buffer,
342                        target_range,
343                        max_attribute_size,
344                    );
345                }
346                (None, None) => self.convert_interleaved_to_interleaved(
347                    source_buffer
348                        .as_interleaved()
349                        .expect("Source buffer must either be an interleaved or columnar buffer"),
350                    source_range,
351                    target_buffer
352                        .as_interleaved_mut()
353                        .expect("Target buffer must either be an interleaved or columnar buffer"),
354                    target_range,
355                    max_attribute_size,
356                ),
357            }
358        }
359    }
360
361    /// Make a default (i.e. untransformed) mapping from `from_attribute` to `to_attribute`. This allows
362    /// that the two attributes have different names, so long as there is a valid conversion from the
363    /// datatype of `from_attribute` to the datatype of `to_attribute`
364    ///
365    /// # Panics
366    ///
367    /// If there is no possible conversion from the datatype of `from_attribute` into the datatype of `to_attribute`
368    fn make_default_mapping(
369        from_attribute: &'a PointAttributeMember,
370        to_attribute: &'a PointAttributeMember,
371    ) -> AttributeMapping<'a> {
372        if from_attribute.datatype() == to_attribute.datatype() {
373            AttributeMapping {
374                target_attribute: to_attribute,
375                source_attribute: from_attribute,
376                converter: None,
377                transformation: None,
378            }
379        } else {
380            let from_datatype = from_attribute.datatype();
381            let to_datatype = to_attribute.datatype();
382            let converter =
383                get_generic_converter(from_datatype, to_datatype).unwrap_or_else(|| {
384                    panic!(
385                        "No conversion from {} to {} possible",
386                        from_datatype, to_datatype
387                    )
388                });
389            AttributeMapping {
390                target_attribute: to_attribute,
391                source_attribute: from_attribute,
392                converter: Some(converter),
393                transformation: None,
394            }
395        }
396    }
397
398    /// Like `make_transformed_mapping`, but with an additional transformation function that gets applied
399    /// to the attribute values during conversion
400    ///
401    /// # Panics
402    ///
403    /// If `T::data_type()` does not match `to_attribute.datatype()`
404    fn make_transformed_mapping<T: PrimitiveType>(
405        from_attribute: &'a PointAttributeMember,
406        to_attribute: &'a PointAttributeMember,
407        transform_fn: impl Fn(T) -> T + 'static,
408        apply_to_source_attribute: bool,
409    ) -> AttributeMapping<'a> {
410        let mut mapping = Self::make_default_mapping(from_attribute, to_attribute);
411        mapping.transformation = Some(Transformation {
412            func: to_untyped_transform_fn(transform_fn),
413            apply_to_source_attribute,
414        });
415        mapping
416    }
417
418    fn convert_columnar_to_columnar(
419        &self,
420        source_buffer: &dyn ColumnarBuffer,
421        source_range: Range<usize>,
422        target_buffer: &mut dyn ColumnarBufferMut,
423        target_range: Range<usize>,
424    ) {
425        for mapping in &self.mappings {
426            let source_attribute_data = source_buffer.get_attribute_range_ref(
427                mapping.source_attribute.attribute_definition(),
428                source_range.clone(),
429            );
430            if let Some(converter) = mapping.converter {
431                let target_attribute_data = target_buffer.get_attribute_range_mut(
432                    mapping.target_attribute.attribute_definition(),
433                    target_range.clone(),
434                );
435                let source_attribute_size = mapping.source_attribute.size() as usize;
436                let target_attribute_size = mapping.target_attribute.size() as usize;
437                let mut source_tmp_buffer: Vec<u8> = vec![0; source_attribute_size];
438                for (source_chunk, target_chunk) in source_attribute_data
439                    .chunks_exact(source_attribute_size)
440                    .zip(target_attribute_data.chunks_exact_mut(target_attribute_size))
441                {
442                    // Safe because we guarantee that the slice sizes match and their data comes from the
443                    // correct attribute
444                    unsafe {
445                        if let Some(transformation) = mapping.transformation.as_ref() {
446                            // If we transform the source attribute, we can't do so in-place (source data is immutable)
447                            // so we have to copy into a temporary buffer
448                            if transformation.apply_to_source_attribute {
449                                source_tmp_buffer.copy_from_slice(source_chunk);
450                                (transformation.func)(&mut source_tmp_buffer[..]);
451                                converter(&source_tmp_buffer[..], target_chunk);
452                            } else {
453                                converter(source_chunk, target_chunk);
454                                (transformation.func)(target_chunk);
455                            }
456                        } else {
457                            converter(source_chunk, target_chunk);
458                        }
459                    }
460                }
461            } else {
462                // Safe because if the source and target attributes would not be equal, there would be a
463                // converter
464                unsafe {
465                    target_buffer.set_attribute_range(
466                        mapping.target_attribute.attribute_definition(),
467                        target_range.clone(),
468                        source_attribute_data,
469                    );
470                }
471                if let Some(transformation) = mapping.transformation.as_ref() {
472                    // Here it is irrelevant whether the transformation should be applied to the source or target attribute,
473                    // as both have the same datatype!
474                    let target_attribute_range = target_buffer.get_attribute_range_mut(
475                        mapping.target_attribute.attribute_definition(),
476                        target_range.clone(),
477                    );
478                    let target_attribute_size = mapping.target_attribute.size() as usize;
479                    for target_chunk in
480                        target_attribute_range.chunks_exact_mut(target_attribute_size)
481                    {
482                        (transformation.func)(target_chunk);
483                    }
484                }
485            }
486        }
487    }
488
489    fn convert_columnar_to_interleaved<'b, B: InterleavedBufferMut<'b> + ?Sized>(
490        &self,
491        source_buffer: &dyn ColumnarBuffer,
492        source_range: Range<usize>,
493        target_buffer: &mut B,
494        target_range: Range<usize>,
495    ) {
496        for mapping in &self.mappings {
497            let source_attribute_data = source_buffer.get_attribute_range_ref(
498                mapping.source_attribute.attribute_definition(),
499                source_range.clone(),
500            );
501            let mut target_attribute_data =
502                target_buffer.view_raw_attribute_mut(mapping.target_attribute);
503            let source_attribute_size = mapping.source_attribute.size() as usize;
504
505            if let Some(converter) = mapping.converter {
506                let mut source_tmp_buffer: Vec<u8> = vec![0; source_attribute_size];
507                for (index, source_chunk) in source_attribute_data
508                    .chunks_exact(source_attribute_size)
509                    .enumerate()
510                {
511                    let target_attribute_chunk =
512                        &mut target_attribute_data[index + target_range.start];
513                    // Safe because we guarantee that the slice sizes match and their data comes from the
514                    // correct attribute
515                    unsafe {
516                        if let Some(transformation) = mapping.transformation.as_ref() {
517                            if transformation.apply_to_source_attribute {
518                                source_tmp_buffer.copy_from_slice(source_chunk);
519                                (transformation.func)(&mut source_tmp_buffer[..]);
520                                converter(&source_tmp_buffer[..], target_attribute_chunk);
521                            } else {
522                                converter(source_chunk, target_attribute_chunk);
523                                (transformation.func)(target_attribute_chunk);
524                            }
525                        } else {
526                            converter(source_chunk, target_attribute_chunk);
527                        }
528                    }
529                }
530            } else {
531                for (index, attribute_data) in source_attribute_data
532                    .chunks_exact(source_attribute_size)
533                    .enumerate()
534                {
535                    let target_attribute_chunk =
536                        &mut target_attribute_data[index + target_range.start];
537                    target_attribute_chunk.copy_from_slice(attribute_data);
538                    if let Some(transformation) = mapping.transformation.as_ref() {
539                        (transformation.func)(target_attribute_chunk);
540                    }
541                }
542            }
543        }
544    }
545
546    fn convert_interleaved_to_columnar<'b, B: InterleavedBuffer<'b> + ?Sized>(
547        &self,
548        source_buffer: &B,
549        source_range: Range<usize>,
550        target_buffer: &mut dyn ColumnarBufferMut,
551        target_range: Range<usize>,
552        max_attribute_size: usize,
553    ) {
554        let mut buffer: Vec<u8> = vec![0; max_attribute_size];
555
556        for mapping in &self.mappings {
557            let source_attribute_data = source_buffer.view_raw_attribute(mapping.source_attribute);
558            let target_attribute_range = target_buffer.get_attribute_range_mut(
559                mapping.target_attribute.attribute_definition(),
560                target_range.clone(),
561            );
562            let target_attribute_size = mapping.target_attribute.size() as usize;
563
564            for (point_index, target_attribute_chunk) in target_attribute_range
565                .chunks_exact_mut(target_attribute_size)
566                .enumerate()
567            {
568                let source_attribute_chunk =
569                    &source_attribute_data[point_index + source_range.start];
570
571                if let Some(converter) = mapping.converter {
572                    if let Some(transformation) = mapping.transformation.as_ref() {
573                        if transformation.apply_to_source_attribute {
574                            let buf = &mut buffer[..source_attribute_chunk.len()];
575                            buf.copy_from_slice(source_attribute_chunk);
576                            (transformation.func)(buf);
577                            unsafe {
578                                converter(buf, target_attribute_chunk);
579                            }
580                        } else {
581                            unsafe {
582                                converter(source_attribute_chunk, target_attribute_chunk);
583                            }
584                            (transformation.func)(target_attribute_chunk);
585                        }
586                    } else {
587                        // Safety: converter came from the source and target PointLayouts
588                        // buffer sizes are correct because they come from the PointAttributeMembers
589                        // set_attribute is correct for the same reasons
590                        unsafe {
591                            converter(source_attribute_chunk, target_attribute_chunk);
592                        }
593                    }
594                } else if let Some(transformation) = mapping.transformation.as_ref() {
595                    let buf = &mut buffer[..source_attribute_chunk.len()];
596                    buf.copy_from_slice(source_attribute_chunk);
597                    (transformation.func)(buf);
598                    target_attribute_chunk.copy_from_slice(buf);
599                } else {
600                    target_attribute_chunk.copy_from_slice(source_attribute_chunk);
601                }
602            }
603        }
604    }
605
606    fn convert_interleaved_to_interleaved<
607        'b,
608        'c,
609        InBuffer: InterleavedBuffer<'b> + ?Sized,
610        OutBuffer: InterleavedBufferMut<'c> + ?Sized,
611    >(
612        &self,
613        source_buffer: &InBuffer,
614        source_range: Range<usize>,
615        target_buffer: &mut OutBuffer,
616        target_range: Range<usize>,
617        max_attribute_size: usize,
618    ) {
619        let mut buffer: Vec<u8> = vec![0; max_attribute_size];
620
621        // For each attribute...
622        for mapping in &self.mappings {
623            let source_attribute_view = source_buffer.view_raw_attribute(mapping.source_attribute);
624            let mut target_attribute_view =
625                target_buffer.view_raw_attribute_mut(mapping.target_attribute);
626
627            // Then apply conversions and transformations for each point
628            for (source_index, target_index) in source_range.clone().zip(target_range.clone()) {
629                let source_attribute_data = &source_attribute_view[source_index];
630                let target_attribute_data = &mut target_attribute_view[target_index];
631
632                if let Some(converter) = mapping.converter {
633                    if let Some(transformation) = mapping.transformation.as_ref() {
634                        if transformation.apply_to_source_attribute {
635                            let buf = &mut buffer[..mapping.source_attribute.size() as usize];
636                            buf.copy_from_slice(source_attribute_data);
637                            (transformation.func)(buf);
638                            unsafe {
639                                converter(buf, target_attribute_data);
640                            }
641                        } else {
642                            unsafe {
643                                converter(source_attribute_data, target_attribute_data);
644                            }
645                            (transformation.func)(target_attribute_data);
646                        }
647                    } else {
648                        unsafe {
649                            converter(source_attribute_data, target_attribute_data);
650                        }
651                    }
652                } else if let Some(transformation) = mapping.transformation.as_ref() {
653                    let buf = &mut buffer[..mapping.source_attribute.size() as usize];
654                    buf.copy_from_slice(source_attribute_data);
655                    (transformation.func)(buf);
656                    target_attribute_data.copy_from_slice(buf);
657                } else {
658                    target_attribute_data.copy_from_slice(source_attribute_data);
659                }
660            }
661        }
662    }
663}
664
665#[cfg(test)]
666mod tests {
667    use std::iter::FromIterator;
668
669    use itertools::Itertools;
670    use nalgebra::Vector3;
671    use rand::{thread_rng, Rng};
672
673    use crate::{
674        containers::{BorrowedBufferExt, HashMapBuffer, VectorBuffer},
675        layout::{
676            attributes::{CLASSIFICATION, POSITION_3D, RETURN_NUMBER},
677            PointType,
678        },
679        test_utils::{CustomPointTypeBig, CustomPointTypeSmall, DefaultPointDistribution},
680    };
681
682    use super::*;
683
684    fn buffer_converter_default_generic<
685        TFrom: for<'a> BorrowedBuffer<'a> + FromIterator<CustomPointTypeBig>,
686        TTo: for<'a> OwningBuffer<'a> + for<'a> MakeBufferFromLayout<'a>,
687    >() {
688        let rng = thread_rng();
689        let source_points = rng
690            .sample_iter::<CustomPointTypeBig, _>(DefaultPointDistribution)
691            .take(16)
692            .collect::<TFrom>();
693
694        // `CustomPointTypeSmall` is a subset of `CustomPointTypeBig`, so this conversion should work!
695        let target_layout = CustomPointTypeSmall::layout();
696        let converter =
697            BufferLayoutConverter::for_layouts(source_points.point_layout(), &target_layout);
698
699        let converted_points = converter.convert::<TTo, _>(&source_points);
700
701        assert_eq!(target_layout, *converted_points.point_layout());
702
703        let expected_positions = source_points
704            .view_attribute::<Vector3<f64>>(&POSITION_3D)
705            .into_iter()
706            .collect_vec();
707        let actual_positions = converted_points
708            .view_attribute::<Vector3<f64>>(&POSITION_3D)
709            .into_iter()
710            .collect_vec();
711        assert_eq!(expected_positions, actual_positions);
712
713        let expected_classifications = source_points
714            .view_attribute::<u8>(&CLASSIFICATION)
715            .into_iter()
716            .collect_vec();
717        let actual_classifications = converted_points
718            .view_attribute::<u8>(&CLASSIFICATION)
719            .into_iter()
720            .collect_vec();
721        assert_eq!(expected_classifications, actual_classifications);
722    }
723
724    fn buffer_converter_multiple_attributes_from_one_generic<
725        TFrom: for<'a> BorrowedBuffer<'a> + FromIterator<CustomPointTypeBig>,
726        TTo: for<'a> OwningBuffer<'a> + for<'a> MakeBufferFromLayout<'a>,
727    >() {
728        let rng = thread_rng();
729        let source_points = rng
730            .sample_iter::<CustomPointTypeBig, _>(DefaultPointDistribution)
731            .take(16)
732            .collect::<TFrom>();
733
734        // Convert a single source attribute (CLASSIFICATION) into two attributes that both have `u8` as their type
735        let custom_layout = PointLayout::from_attributes(&[CLASSIFICATION, RETURN_NUMBER]);
736
737        let mut converter = BufferLayoutConverter::for_layouts_with_default(
738            source_points.point_layout(),
739            &custom_layout,
740        );
741        converter.set_custom_mapping(&CLASSIFICATION, &RETURN_NUMBER);
742
743        let converted_points = converter.convert::<TTo, _>(&source_points);
744        assert_eq!(custom_layout, *converted_points.point_layout());
745
746        let expected_classifications = source_points
747            .view_attribute::<u8>(&CLASSIFICATION)
748            .into_iter()
749            .collect_vec();
750        let actual_classifications = converted_points
751            .view_attribute::<u8>(&CLASSIFICATION)
752            .into_iter()
753            .collect_vec();
754        let actual_return_numbers = converted_points
755            .view_attribute::<u8>(&RETURN_NUMBER)
756            .into_iter()
757            .collect_vec();
758
759        assert_eq!(expected_classifications, actual_classifications);
760        // Yes classifications, this test checks that we can convert two attributes from the same source attribute!
761        assert_eq!(expected_classifications, actual_return_numbers);
762    }
763
764    fn buffer_converter_transformed_target_attribute_generic<
765        TFrom: for<'a> BorrowedBuffer<'a> + FromIterator<CustomPointTypeBig>,
766        TTo: for<'a> OwningBuffer<'a> + for<'a> MakeBufferFromLayout<'a>,
767    >() {
768        let rng = thread_rng();
769        let source_points = rng
770            .sample_iter::<CustomPointTypeBig, _>(DefaultPointDistribution)
771            .take(16)
772            .collect::<TFrom>();
773
774        let custom_layout = PointLayout::from_attributes(&[POSITION_3D]);
775
776        let mut converter = BufferLayoutConverter::for_layouts_with_default(
777            source_points.point_layout(),
778            &custom_layout,
779        );
780        const OFFSET: f64 = 42.0;
781        let transform_positions_fn =
782            |source_position: Vector3<f64>| -> Vector3<f64> { source_position.add_scalar(OFFSET) };
783        converter.set_custom_mapping_with_transformation(
784            &POSITION_3D,
785            &POSITION_3D,
786            transform_positions_fn,
787            false,
788        );
789
790        let converted_points = converter.convert::<TTo, _>(&source_points);
791        assert_eq!(custom_layout, *converted_points.point_layout());
792
793        let expected_positions = source_points
794            .view_attribute::<Vector3<f64>>(&POSITION_3D)
795            .into_iter()
796            .map(transform_positions_fn)
797            .collect_vec();
798        let actual_positions = converted_points
799            .view_attribute::<Vector3<f64>>(&POSITION_3D)
800            .into_iter()
801            .collect_vec();
802
803        assert_eq!(expected_positions, actual_positions);
804    }
805
806    fn buffer_converter_transformed_source_attribute_generic<
807        TFrom: for<'a> BorrowedBuffer<'a> + FromIterator<CustomPointTypeBig>,
808        TTo: for<'a> OwningBuffer<'a> + for<'a> MakeBufferFromLayout<'a>,
809    >() {
810        let rng = thread_rng();
811        let source_points = rng
812            .sample_iter::<CustomPointTypeBig, _>(DefaultPointDistribution)
813            .take(16)
814            .collect::<TFrom>();
815
816        let custom_layout = PointLayout::from_attributes(&[POSITION_3D]);
817
818        let mut converter = BufferLayoutConverter::for_layouts_with_default(
819            source_points.point_layout(),
820            &custom_layout,
821        );
822        const OFFSET: f64 = 42.0;
823        let transform_positions_fn =
824            |source_position: Vector3<f64>| -> Vector3<f64> { source_position.add_scalar(OFFSET) };
825        converter.set_custom_mapping_with_transformation(
826            &POSITION_3D,
827            &POSITION_3D,
828            transform_positions_fn,
829            true,
830        );
831
832        let converted_points = converter.convert::<TTo, _>(&source_points);
833        assert_eq!(custom_layout, *converted_points.point_layout());
834
835        let expected_positions = source_points
836            .view_attribute::<Vector3<f64>>(&POSITION_3D)
837            .into_iter()
838            .map(transform_positions_fn)
839            .collect_vec();
840        let actual_positions = converted_points
841            .view_attribute::<Vector3<f64>>(&POSITION_3D)
842            .into_iter()
843            .collect_vec();
844
845        assert_eq!(expected_positions, actual_positions);
846    }
847
848    fn buffer_converter_identity_generic<
849        TFrom: for<'a> BorrowedBuffer<'a> + FromIterator<CustomPointTypeBig>,
850        TTo: for<'a> OwningBuffer<'a> + for<'a> MakeBufferFromLayout<'a>,
851    >() {
852        let rng = thread_rng();
853        let source_points = rng
854            .sample_iter::<CustomPointTypeBig, _>(DefaultPointDistribution)
855            .take(16)
856            .collect::<TFrom>();
857
858        let converter = BufferLayoutConverter::for_layouts_with_default(
859            source_points.point_layout(),
860            source_points.point_layout(),
861        );
862        let converted_points = converter.convert::<TTo, _>(&source_points);
863
864        let expected_points = source_points
865            .view::<CustomPointTypeBig>()
866            .into_iter()
867            .collect_vec();
868        let actual_points = converted_points
869            .view::<CustomPointTypeBig>()
870            .into_iter()
871            .collect_vec();
872        assert_eq!(expected_points, actual_points);
873    }
874
875    #[test]
876    fn test_buffer_converter_default() {
877        buffer_converter_default_generic::<VectorBuffer, VectorBuffer>();
878        buffer_converter_default_generic::<VectorBuffer, HashMapBuffer>();
879        buffer_converter_default_generic::<HashMapBuffer, VectorBuffer>();
880        buffer_converter_default_generic::<HashMapBuffer, HashMapBuffer>();
881    }
882
883    #[test]
884    fn test_buffer_converter_multiple_attributes_from_one() {
885        buffer_converter_multiple_attributes_from_one_generic::<VectorBuffer, VectorBuffer>();
886        buffer_converter_multiple_attributes_from_one_generic::<VectorBuffer, HashMapBuffer>();
887        buffer_converter_multiple_attributes_from_one_generic::<HashMapBuffer, VectorBuffer>();
888        buffer_converter_multiple_attributes_from_one_generic::<HashMapBuffer, HashMapBuffer>();
889    }
890
891    #[test]
892    fn test_buffer_converter_transformed_attribute() {
893        buffer_converter_transformed_source_attribute_generic::<VectorBuffer, VectorBuffer>();
894        buffer_converter_transformed_source_attribute_generic::<VectorBuffer, HashMapBuffer>();
895        buffer_converter_transformed_source_attribute_generic::<HashMapBuffer, VectorBuffer>();
896        buffer_converter_transformed_source_attribute_generic::<HashMapBuffer, HashMapBuffer>();
897
898        buffer_converter_transformed_target_attribute_generic::<VectorBuffer, VectorBuffer>();
899        buffer_converter_transformed_target_attribute_generic::<VectorBuffer, HashMapBuffer>();
900        buffer_converter_transformed_target_attribute_generic::<HashMapBuffer, VectorBuffer>();
901        buffer_converter_transformed_target_attribute_generic::<HashMapBuffer, HashMapBuffer>();
902    }
903
904    #[test]
905    fn test_buffer_converter_identity() {
906        buffer_converter_identity_generic::<VectorBuffer, VectorBuffer>();
907        buffer_converter_identity_generic::<VectorBuffer, HashMapBuffer>();
908        buffer_converter_identity_generic::<HashMapBuffer, VectorBuffer>();
909        buffer_converter_identity_generic::<HashMapBuffer, HashMapBuffer>();
910    }
911
912    #[test]
913    #[should_panic]
914    fn test_buffer_converter_mismatched_len() {
915        const COUNT: usize = 16;
916        let rng = thread_rng();
917        let source_points = rng
918            .sample_iter::<CustomPointTypeBig, _>(DefaultPointDistribution)
919            .take(COUNT)
920            .collect::<VectorBuffer>();
921
922        let mut target_buffer =
923            VectorBuffer::with_capacity(COUNT / 2, source_points.point_layout().clone());
924
925        let converter: BufferLayoutConverter<'_> = BufferLayoutConverter::for_layouts_with_default(
926            source_points.point_layout(),
927            source_points.point_layout(),
928        );
929        converter.convert_into(&source_points, &mut target_buffer);
930    }
931}