use std::{alloc::Layout, borrow::Cow, fmt::Display, iter::FromIterator, ops::Range};
use itertools::Itertools;
use nalgebra::{Vector3, Vector4};
use static_assertions::const_assert;
use uuid::Uuid;
use crate::math::Alignable;
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum PointAttributeDataType {
U8,
I8,
U16,
I16,
U32,
I32,
U64,
I64,
F32,
F64,
Vec3u8,
Vec3u16,
Vec3f32,
Vec3i32,
Vec3f64,
Vec4u8,
ByteArray(u64),
Custom {
size: u64,
min_alignment: u64,
name: Uuid,
}, }
impl PointAttributeDataType {
pub const fn size(&self) -> u64 {
match self {
PointAttributeDataType::U8 => 1,
PointAttributeDataType::I8 => 1,
PointAttributeDataType::U16 => 2,
PointAttributeDataType::I16 => 2,
PointAttributeDataType::U32 => 4,
PointAttributeDataType::I32 => 4,
PointAttributeDataType::U64 => 8,
PointAttributeDataType::I64 => 8,
PointAttributeDataType::F32 => 4,
PointAttributeDataType::F64 => 8,
PointAttributeDataType::Vec3u8 => 3,
PointAttributeDataType::Vec3u16 => 6,
PointAttributeDataType::Vec3i32 => 12,
PointAttributeDataType::Vec3f32 => 12,
PointAttributeDataType::Vec3f64 => 24,
PointAttributeDataType::Vec4u8 => 4,
PointAttributeDataType::ByteArray(length) => *length,
PointAttributeDataType::Custom {
size,
min_alignment: _,
name: _,
} => *size,
}
}
pub fn min_alignment(&self) -> u64 {
let align = match self {
PointAttributeDataType::U8 => std::mem::align_of::<u8>(),
PointAttributeDataType::I8 => std::mem::align_of::<i8>(),
PointAttributeDataType::U16 => std::mem::align_of::<u16>(),
PointAttributeDataType::I16 => std::mem::align_of::<i16>(),
PointAttributeDataType::U32 => std::mem::align_of::<u32>(),
PointAttributeDataType::I32 => std::mem::align_of::<i32>(),
PointAttributeDataType::U64 => std::mem::align_of::<u64>(),
PointAttributeDataType::I64 => std::mem::align_of::<i64>(),
PointAttributeDataType::F32 => std::mem::align_of::<f32>(),
PointAttributeDataType::F64 => std::mem::align_of::<f64>(),
PointAttributeDataType::Vec3u8 => std::mem::align_of::<Vector3<u8>>(),
PointAttributeDataType::Vec3u16 => std::mem::align_of::<Vector3<u16>>(),
PointAttributeDataType::Vec3i32 => std::mem::align_of::<Vector3<i32>>(),
PointAttributeDataType::Vec3f32 => std::mem::align_of::<Vector3<f32>>(),
PointAttributeDataType::Vec3f64 => std::mem::align_of::<Vector3<f64>>(),
PointAttributeDataType::Vec4u8 => std::mem::align_of::<Vector4<u8>>(),
PointAttributeDataType::ByteArray(_) => 1,
PointAttributeDataType::Custom {
size: _,
min_alignment,
name: _,
} => *min_alignment as usize,
};
align as u64
}
}
impl Display for PointAttributeDataType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
PointAttributeDataType::U8 => write!(f, "U8"),
PointAttributeDataType::I8 => write!(f, "I8"),
PointAttributeDataType::U16 => write!(f, "U16"),
PointAttributeDataType::I16 => write!(f, "I16"),
PointAttributeDataType::U32 => write!(f, "U32"),
PointAttributeDataType::I32 => write!(f, "I32"),
PointAttributeDataType::U64 => write!(f, "U64"),
PointAttributeDataType::I64 => write!(f, "I64"),
PointAttributeDataType::F32 => write!(f, "F32"),
PointAttributeDataType::F64 => write!(f, "F64"),
PointAttributeDataType::Vec3u8 => write!(f, "Vec3<u8>"),
PointAttributeDataType::Vec3u16 => write!(f, "Vec3<u16>"),
PointAttributeDataType::Vec3i32 => write!(f, "Vec3<i32>"),
PointAttributeDataType::Vec3f32 => write!(f, "Vec3<f32>"),
PointAttributeDataType::Vec3f64 => write!(f, "Vec3<f64>"),
PointAttributeDataType::Vec4u8 => write!(f, "Vec4<u8>"),
PointAttributeDataType::ByteArray(length) => write!(f, "ByteArray[{length}]"),
PointAttributeDataType::Custom {
size: _,
min_alignment: _,
name,
} => write!(f, "{name}"),
}
}
}
pub trait PrimitiveType: Copy + bytemuck::Pod {
fn data_type() -> PointAttributeDataType;
}
impl PrimitiveType for u8 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::U8
}
}
impl PrimitiveType for u16 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::U16
}
}
impl PrimitiveType for u32 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::U32
}
}
impl PrimitiveType for u64 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::U64
}
}
impl PrimitiveType for i8 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::I8
}
}
impl PrimitiveType for i16 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::I16
}
}
impl PrimitiveType for i32 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::I32
}
}
impl PrimitiveType for i64 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::I64
}
}
impl PrimitiveType for f32 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::F32
}
}
impl PrimitiveType for f64 {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::F64
}
}
impl PrimitiveType for Vector3<u8> {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::Vec3u8
}
}
impl PrimitiveType for Vector3<u16> {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::Vec3u16
}
}
impl PrimitiveType for Vector3<i32> {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::Vec3i32
}
}
impl PrimitiveType for Vector3<f32> {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::Vec3f32
}
}
impl PrimitiveType for Vector3<f64> {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::Vec3f64
}
}
impl PrimitiveType for Vector4<u8> {
fn data_type() -> PointAttributeDataType {
PointAttributeDataType::Vec4u8
}
}
const_assert!(std::mem::size_of::<Vector3<u8>>() == 3);
const_assert!(std::mem::size_of::<Vector3<u16>>() == 6);
const_assert!(std::mem::size_of::<Vector3<f32>>() == 12);
const_assert!(std::mem::size_of::<Vector3<f64>>() == 24);
const_assert!(std::mem::size_of::<Vector4<u8>>() == 4);
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PointAttributeDefinition {
name: Cow<'static, str>,
datatype: PointAttributeDataType,
}
impl PointAttributeDefinition {
pub const fn custom(name: Cow<'static, str>, datatype: PointAttributeDataType) -> Self {
Self { name, datatype }
}
pub fn name(&self) -> &str {
&self.name
}
#[inline]
pub const fn datatype(&self) -> PointAttributeDataType {
self.datatype
}
#[inline]
pub const fn size(&self) -> u64 {
self.datatype.size()
}
pub fn with_custom_datatype(&self, new_datatype: PointAttributeDataType) -> Self {
Self {
name: self.name.clone(),
datatype: new_datatype,
}
}
pub fn at_offset_in_type(&self, offset: u64) -> PointAttributeMember {
PointAttributeMember {
attribute_definition: self.clone(),
offset,
size: self.size(),
}
}
}
impl Display for PointAttributeDefinition {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{};{}]", self.name, self.datatype)
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PointAttributeMember {
attribute_definition: PointAttributeDefinition,
offset: u64,
size: u64,
}
impl PointAttributeMember {
pub fn custom(name: &'static str, datatype: PointAttributeDataType, offset: u64) -> Self {
Self {
attribute_definition: PointAttributeDefinition {
name: Cow::Borrowed(name),
datatype,
},
offset,
size: datatype.size(),
}
}
pub fn name(&self) -> &str {
self.attribute_definition.name()
}
#[inline]
pub const fn datatype(&self) -> PointAttributeDataType {
self.attribute_definition.datatype()
}
#[inline]
pub const fn offset(&self) -> u64 {
self.offset
}
pub fn attribute_definition(&self) -> &PointAttributeDefinition {
&self.attribute_definition
}
#[inline]
pub const fn size(&self) -> u64 {
self.size
}
pub fn byte_range_within_point(&self) -> Range<usize> {
let start = self.offset as usize;
let end = start + self.size() as usize;
start..end
}
}
impl Display for PointAttributeMember {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"[{};{} @ offset {}]",
self.name(),
self.datatype(),
self.offset
)
}
}
pub mod attributes {
use std::borrow::Cow;
use super::{PointAttributeDataType, PointAttributeDefinition};
pub const POSITION_3D: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("Position3D"),
datatype: PointAttributeDataType::Vec3f64,
};
pub const INTENSITY: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("Intensity"),
datatype: PointAttributeDataType::U16,
};
pub const RETURN_NUMBER: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("ReturnNumber"),
datatype: PointAttributeDataType::U8,
};
pub const NUMBER_OF_RETURNS: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("NumberOfReturns"),
datatype: PointAttributeDataType::U8,
};
pub const CLASSIFICATION_FLAGS: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("ClassificationFlags"),
datatype: PointAttributeDataType::U8,
};
pub const SCANNER_CHANNEL: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("ScannerChannel"),
datatype: PointAttributeDataType::U8,
};
pub const SCAN_DIRECTION_FLAG: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("ScanDirectionFlag"),
datatype: PointAttributeDataType::U8,
};
pub const EDGE_OF_FLIGHT_LINE: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("EdgeOfFlightLine"),
datatype: PointAttributeDataType::U8,
};
pub const CLASSIFICATION: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("Classification"),
datatype: PointAttributeDataType::U8,
};
pub const SCAN_ANGLE_RANK: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("ScanAngleRank"),
datatype: PointAttributeDataType::I8,
};
pub const SCAN_ANGLE: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("ScanAngle"),
datatype: PointAttributeDataType::I16,
};
pub const USER_DATA: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("UserData"),
datatype: PointAttributeDataType::U8,
};
pub const POINT_SOURCE_ID: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("PointSourceID"),
datatype: PointAttributeDataType::U16,
};
pub const COLOR_RGB: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("ColorRGB"),
datatype: PointAttributeDataType::Vec3u16,
};
pub const GPS_TIME: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("GpsTime"),
datatype: PointAttributeDataType::F64,
};
pub const NIR: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("NIR"),
datatype: PointAttributeDataType::U16,
};
pub const WAVE_PACKET_DESCRIPTOR_INDEX: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("WavePacketDescriptorIndex"),
datatype: PointAttributeDataType::U8,
};
pub const WAVEFORM_DATA_OFFSET: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("WaveformDataOffset"),
datatype: PointAttributeDataType::U64,
};
pub const WAVEFORM_PACKET_SIZE: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("WaveformPacketSize"),
datatype: PointAttributeDataType::U32,
};
pub const RETURN_POINT_WAVEFORM_LOCATION: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("ReturnPointWaveformLocation"),
datatype: PointAttributeDataType::F32,
};
pub const WAVEFORM_PARAMETERS: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("WaveformParameters"),
datatype: PointAttributeDataType::Vec3f32,
};
pub const POINT_ID: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("PointID"),
datatype: PointAttributeDataType::U64,
};
pub const NORMAL: PointAttributeDefinition = PointAttributeDefinition {
name: Cow::Borrowed("Normal"),
datatype: PointAttributeDataType::Vec3f32,
};
}
pub enum FieldAlignment {
Default,
Packed(u64),
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct PointLayout {
attributes: Vec<PointAttributeMember>,
#[cfg_attr(feature = "serde", serde(with = "serde_layout"))]
memory_layout: Layout,
}
impl PointLayout {
pub fn from_attributes(attributes: &[PointAttributeDefinition]) -> Self {
attributes.iter().cloned().collect()
}
pub fn from_attributes_packed(
attributes: &[PointAttributeDefinition],
max_alignment: u64,
) -> Self {
let mut layout = Self::default();
for attribute in attributes {
layout.add_attribute(attribute.clone(), FieldAlignment::Packed(max_alignment));
}
layout
}
pub fn from_members_and_alignment(
attributes: &[PointAttributeMember],
type_alignment: u64,
) -> Self {
let unique_names = attributes.iter().map(|a| a.name()).unique();
if unique_names.count() != attributes.len() {
panic!(
"PointLayout::from_attributes_and_offsets: All attributes must have unique names!"
);
}
let mut unaligned_ranges = attributes
.iter()
.map(|a| (a.offset()..(a.offset() + a.size())))
.collect::<Vec<_>>();
unaligned_ranges.sort_by(|a, b| a.start.cmp(&b.start));
for next_idx in 1..unaligned_ranges.len() {
let this_range = &unaligned_ranges[next_idx - 1];
let next_range = &unaligned_ranges[next_idx];
if this_range.end > next_range.start {
panic!("PointLayout::from_attributes_and_offsets: All attributes must span non-overlapping memory regions!")
}
}
let unaligned_size = attributes
.iter()
.max_by(|a, b| a.offset().cmp(&b.offset()))
.map(|last_attribute| last_attribute.offset() + last_attribute.size())
.unwrap_or(0);
Self {
attributes: attributes.to_vec(),
memory_layout: Layout::from_size_align(
unaligned_size.align_to(type_alignment) as usize,
type_alignment as usize,
)
.expect("Could not create memory layout for PointLayout"),
}
}
pub fn add_attribute(
&mut self,
point_attribute: PointAttributeDefinition,
field_alignment: FieldAlignment,
) {
if let Some(old_attribute) = self.get_attribute_by_name(point_attribute.name()) {
panic!(
"Point attribute {} is already present in this PointLayout!",
old_attribute.name()
);
}
let alignment_requirement_of_field = match field_alignment {
FieldAlignment::Default => point_attribute.datatype().min_alignment(),
FieldAlignment::Packed(max_alignment) => {
std::cmp::min(max_alignment, point_attribute.datatype().min_alignment())
}
};
let offset = self
.packed_offset_of_next_field()
.align_to(alignment_requirement_of_field);
let current_max_alignment = self.memory_layout.align() as u64;
let new_max_alignment = match field_alignment {
FieldAlignment::Default => std::cmp::max(
current_max_alignment,
point_attribute.datatype().min_alignment(),
),
FieldAlignment::Packed(max_alignment) => {
std::cmp::min(max_alignment, current_max_alignment)
}
};
self.attributes
.push(point_attribute.at_offset_in_type(offset));
let old_size = self.memory_layout.size() as u64;
let attribute_end = offset + point_attribute.size();
let new_size_unaligned = std::cmp::max(old_size, attribute_end);
self.memory_layout = Layout::from_size_align(
new_size_unaligned.align_to(new_max_alignment) as usize,
new_max_alignment as usize,
)
.expect("Could not create memory layout for PointLayout");
}
pub fn has_attribute_with_name(&self, attribute_name: &str) -> bool {
self.attributes
.iter()
.any(|attribute| attribute.name() == attribute_name)
}
pub fn has_attribute(&self, attribute: &PointAttributeDefinition) -> bool {
self.attributes.iter().any(|this_attribute| {
this_attribute.name() == attribute.name()
&& this_attribute.datatype() == attribute.datatype()
})
}
pub fn get_attribute(
&self,
attribute: &PointAttributeDefinition,
) -> Option<&PointAttributeMember> {
self.attributes.iter().find(|self_attribute| {
self_attribute.name() == attribute.name()
&& self_attribute.datatype() == attribute.datatype()
})
}
pub fn get_attribute_by_name(&self, attribute_name: &str) -> Option<&PointAttributeMember> {
self.attributes
.iter()
.find(|attribute| attribute.name() == attribute_name)
}
pub fn at(&self, index: usize) -> &PointAttributeMember {
&self.attributes[index]
}
pub fn attributes(&self) -> impl Iterator<Item = &PointAttributeMember> {
self.attributes.iter()
}
#[inline]
pub const fn size_of_point_entry(&self) -> u64 {
self.memory_layout.size() as u64
}
pub fn index_of(&self, attribute: &PointAttributeDefinition) -> Option<usize> {
self.attributes.iter().position(|this_attribute| {
this_attribute.name() == attribute.name()
&& this_attribute.datatype() == attribute.datatype()
})
}
pub fn compare_without_offsets(&self, other: &PointLayout) -> bool {
if self.attributes.len() != other.attributes.len() {
return false;
}
self.attributes.iter().all(|self_attribute| {
other
.get_attribute_by_name(self_attribute.name())
.map(|other_attribute| other_attribute.datatype() == self_attribute.datatype())
.unwrap_or(false)
})
}
pub fn offset_of(&self, attribute: &PointAttributeDefinition) -> Option<u64> {
self.attributes
.iter()
.find(|this_attribute| {
this_attribute.name() == attribute.name()
&& this_attribute.datatype() == attribute.datatype()
})
.map(|member| member.offset())
}
fn packed_offset_of_next_field(&self) -> u64 {
if self.attributes.is_empty() {
0
} else {
let last_attribute = self.attributes.last().unwrap();
last_attribute.offset() + last_attribute.size()
}
}
}
impl Display for PointLayout {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
writeln!(f, "PointLayout {{")?;
for attribute in self.attributes() {
writeln!(f, "\t{}", attribute)?;
}
writeln!(f, "}}")
}
}
impl Default for PointLayout {
fn default() -> Self {
Self {
attributes: vec![],
memory_layout: Layout::from_size_align(0, 1).unwrap(),
}
}
}
impl FromIterator<PointAttributeDefinition> for PointLayout {
fn from_iter<T: IntoIterator<Item = PointAttributeDefinition>>(iter: T) -> Self {
let mut layout = Self::default();
for attribute in iter.into_iter() {
layout.add_attribute(attribute.clone(), FieldAlignment::Default);
}
layout
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::layout::{
attributes::{COLOR_RGB, INTENSITY, POSITION_3D},
PointType,
};
use pasture_derive::PointType;
#[derive(
Debug, PointType, Copy, Clone, PartialEq, bytemuck::NoUninit, bytemuck::AnyBitPattern,
)]
#[repr(C, packed)]
struct TestPoint1 {
#[pasture(BUILTIN_POSITION_3D)]
position: Vector3<f64>,
#[pasture(BUILTIN_COLOR_RGB)]
color: Vector3<u16>,
#[pasture(BUILTIN_INTENSITY)]
intensity: u16,
}
#[test]
fn test_derive_point_type() {
let expected_layout_1 = PointLayout::from_attributes_packed(
&[
POSITION_3D.with_custom_datatype(PointAttributeDataType::Vec3f64),
COLOR_RGB.with_custom_datatype(PointAttributeDataType::Vec3u16),
INTENSITY.with_custom_datatype(PointAttributeDataType::U16),
],
1,
);
assert_eq!(expected_layout_1, TestPoint1::layout());
}
#[test]
#[cfg(feature = "serde")]
fn test_point_layout_serde() {
let original_value = PointLayout {
attributes: vec![],
memory_layout: Layout::from_size_align(20, 4).unwrap(),
};
let serialized = serde_json::to_value(original_value.clone()).unwrap();
let expected = json!({
"attributes": [],
"memory_layout": {
"align": 4,
"size": 20
},
});
assert_eq!(serialized, expected);
let deserialized: PointLayout = serde_json::from_value(serialized).unwrap();
assert_eq!(deserialized, original_value);
}
}
#[cfg(feature = "serde")]
mod serde_layout {
use std::alloc::Layout;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
#[derive(Serialize, Deserialize)]
#[serde(rename = "Layout")]
struct SizeAndAlignment {
size: usize,
align: usize,
}
pub fn serialize<S>(layout: &Layout, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
SizeAndAlignment {
size: layout.size(),
align: layout.align(),
}
.serialize(serializer)
}
pub fn deserialize<'de, D>(de: D) -> Result<Layout, D::Error>
where
D: Deserializer<'de>,
{
let fields = SizeAndAlignment::deserialize(de)?;
Layout::from_size_align(fields.size, fields.align).map_err(serde::de::Error::custom)
}
}