use crate::compression_config::EncodedGeometryType;
use crate::draco_types::DataType;
use crate::encoder_buffer::EncoderBuffer;
use crate::encoder_options::EncoderOptions;
use crate::geometry_attribute::GeometryAttributeType;
use crate::geometry_attribute::PointAttribute;
use crate::geometry_indices::PointIndex;
use crate::kd_tree_attributes_encoder::KdTreeAttributesEncoder;
use crate::mesh::Mesh;
use crate::mesh_encoder::EncodedAttributeInfo;
use crate::metadata::METADATA_FLAG_MASK;
use crate::point_cloud::PointCloud;
use crate::sequential_attribute_encoder::{
select_sequential_encoder, SequentialAttributeEncoderType,
};
use crate::sequential_integer_attribute_encoder::SequentialIntegerAttributeEncoder;
use crate::sequential_normal_attribute_encoder::SequentialNormalAttributeEncoder;
use crate::status::{DracoError, Status};
use crate::version::{
has_header_flags, uses_varint_encoding, uses_varint_unique_id, DEFAULT_POINT_CLOUD_VERSION,
};
use crate::corner_table::CornerTable;
pub(crate) fn validate_encodable_attributes(point_cloud: &PointCloud) -> Status {
if point_cloud.num_points() > i32::MAX as usize {
return Err(DracoError::general(format!(
"Geometry has {} points, past the {} a Draco header can carry",
point_cloud.num_points(),
i32::MAX
)));
}
for att_id in 0..point_cloud.num_attributes() {
let attribute = point_cloud.attribute(att_id);
if attribute.num_components() == 0 {
return Err(DracoError::general(format!(
"Attribute {att_id} has zero components and cannot be encoded"
)));
}
if attribute.data_type() == DataType::Invalid {
return Err(DracoError::general(format!(
"Attribute {att_id} has an invalid data type and cannot be encoded"
)));
}
let num_values = attribute.size();
let num_points = point_cloud.num_points();
if attribute.is_mapping_identity() {
if num_points > num_values {
return Err(DracoError::general(format!(
"Attribute {att_id} holds {num_values} values for {num_points} points"
)));
}
} else {
for point in 0..num_points {
let value = attribute.mapped_index(PointIndex(point as u32));
if (value.0 as usize) >= num_values {
return Err(DracoError::general(format!(
"Attribute {att_id} maps point {point} to value {} but holds \
{num_values} values",
value.0
)));
}
}
}
validate_attribute_storage(att_id, attribute)?;
}
Ok(())
}
fn validate_attribute_storage(att_id: i32, attribute: &PointAttribute) -> Status {
let num_values = attribute.size();
if num_values == 0 {
return Ok(());
}
let component_size = attribute.data_type().byte_length();
let element_size = (attribute.num_components() as usize).saturating_mul(component_size);
let byte_stride = attribute.byte_stride().max(0) as usize;
if byte_stride < element_size {
return Err(DracoError::general(format!(
"Attribute {att_id} declares a {byte_stride}-byte stride for {element_size}-byte \
values"
)));
}
let required = (num_values - 1)
.checked_mul(byte_stride)
.and_then(|last_offset| last_offset.checked_add(element_size))
.ok_or_else(|| DracoError::general(format!("Attribute {att_id} value extent overflows")))?;
let available = attribute.buffer().data_size();
if available < required {
return Err(DracoError::general(format!(
"Attribute {att_id} needs {required} bytes for {num_values} values but its buffer \
holds {available}"
)));
}
Ok(())
}
fn point_order(pc: &PointCloud, options: &EncoderOptions) -> Vec<PointIndex> {
let identity = || (0..pc.num_points()).map(|i| PointIndex(i as u32)).collect();
if !options.spatial_point_order() {
return identity();
}
let Some(order) = morton_point_order(pc, options) else {
return identity();
};
order
}
fn curve_axis_bits(options: &EncoderOptions, att_id: i32) -> u32 {
const MAX_AXIS_BITS: i32 = 21;
let quantization = options.get_attribute_int(att_id, "quantization_bits", -1);
if quantization <= 0 {
return MAX_AXIS_BITS as u32;
}
quantization.min(MAX_AXIS_BITS) as u32
}
fn morton_point_order(pc: &PointCloud, options: &EncoderOptions) -> Option<Vec<PointIndex>> {
let att_id = (0..pc.num_attributes())
.find(|id| pc.attribute(*id).attribute_type() == GeometryAttributeType::Position)?;
let attribute = pc.attribute(att_id);
if attribute.num_components() < 3 {
return None;
}
let num_points = pc.num_points();
let mut coordinates = Vec::with_capacity(num_points * 3);
let mut min = [f64::INFINITY; 3];
let mut max = [f64::NEG_INFINITY; 3];
for point in 0..num_points {
let value_index = attribute.mapped_index(PointIndex(point as u32));
for (axis, (low, high)) in min.iter_mut().zip(max.iter_mut()).enumerate() {
let value = read_component_as_f64(attribute, value_index, axis)?;
if !value.is_finite() {
return None;
}
*low = low.min(value);
*high = high.max(value);
coordinates.push(value);
}
}
let axis_bits = curve_axis_bits(options, att_id);
let levels = ((1u64 << axis_bits) - 1) as f64;
let spread = |v: u32| -> u64 {
let mut x = u64::from(v) & 0x1f_ffff;
x = (x | (x << 32)) & 0x001f_0000_0000_ffff;
x = (x | (x << 16)) & 0x001f_0000_ff00_00ff;
x = (x | (x << 8)) & 0x100f_00f0_0f00_f00f;
x = (x | (x << 4)) & 0x10c3_0c30_c30c_30c3;
x = (x | (x << 2)) & 0x1249_2492_4924_9249;
x
};
let mut keyed: Vec<(u64, u32)> = (0..num_points)
.map(|point| {
let mut key = 0u64;
for (axis, (low, high)) in min.iter().zip(max.iter()).enumerate() {
let span = high - low;
let normalized = if span > 0.0 {
(coordinates[point * 3 + axis] - low) / span
} else {
0.0
};
key |= spread((normalized * levels) as u32) << axis;
}
(key, point as u32)
})
.collect();
keyed.sort_unstable();
Some(
keyed
.into_iter()
.map(|(_, point)| PointIndex(point))
.collect(),
)
}
fn read_component_as_f64(
attribute: &PointAttribute,
value_index: crate::geometry_indices::AttributeValueIndex,
component: usize,
) -> Option<f64> {
let data_type = attribute.data_type();
let size = data_type.byte_length();
let offset = value_index.0 as usize * attribute.byte_stride() as usize + component * size;
let mut bytes = [0u8; 8];
attribute.buffer().read(offset, &mut bytes[..size]);
Some(match data_type {
DataType::Float32 => f32::from_le_bytes(bytes[..4].try_into().ok()?) as f64,
DataType::Float64 => f64::from_le_bytes(bytes),
DataType::Int8 => bytes[0] as i8 as f64,
DataType::Uint8 => bytes[0] as f64,
DataType::Int16 => i16::from_le_bytes(bytes[..2].try_into().ok()?) as f64,
DataType::Uint16 => u16::from_le_bytes(bytes[..2].try_into().ok()?) as f64,
DataType::Int32 => i32::from_le_bytes(bytes[..4].try_into().ok()?) as f64,
DataType::Uint32 => u32::from_le_bytes(bytes[..4].try_into().ok()?) as f64,
DataType::Int64 => i64::from_le_bytes(bytes) as f64,
DataType::Uint64 => u64::from_le_bytes(bytes) as f64,
DataType::Bool | DataType::Invalid => return None,
})
}
fn select_encoding_method(
point_cloud: &PointCloud,
options: &EncoderOptions,
) -> Result<i32, DracoError> {
const SEQUENTIAL: i32 = 0;
const KD_TREE: i32 = 1;
let requested = options.get_encoding_method();
if requested == Some(SEQUENTIAL) {
return Ok(SEQUENTIAL);
}
if requested.is_none() && options.get_speed() == 10 {
return Ok(SEQUENTIAL);
}
let mut kd_tree_possible = true;
for att_id in 0..point_cloud.num_attributes() {
let attribute = point_cloud.attribute(att_id);
let data_type = attribute.data_type();
if !matches!(
data_type,
DataType::Float32
| DataType::Uint32
| DataType::Uint16
| DataType::Uint8
| DataType::Int32
| DataType::Int16
| DataType::Int8
) {
kd_tree_possible = false;
}
if kd_tree_possible
&& data_type == DataType::Float32
&& options.get_attribute_int(att_id, "quantization_bits", -1) <= 0
{
kd_tree_possible = false; }
if !kd_tree_possible {
break;
}
}
if kd_tree_possible {
return Ok(KD_TREE);
}
if requested == Some(KD_TREE) {
return Err(DracoError::general("Invalid encoding method.".to_string()));
}
Ok(SEQUENTIAL)
}
pub trait GeometryEncoder {
fn point_cloud(&self) -> Option<&PointCloud>;
fn mesh(&self) -> Option<&Mesh>;
fn corner_table(&self) -> Option<&CornerTable>;
fn options(&self) -> &EncoderOptions;
fn get_geometry_type(&self) -> EncodedGeometryType;
fn get_encoding_method(&self) -> Option<i32> {
None
}
fn get_data_to_corner_map(&self) -> Option<&[u32]> {
None
}
fn get_vertex_to_data_map(&self) -> Option<&[i32]> {
None
}
fn get_portable_attribute(
&self,
_att_id: i32,
) -> Option<&crate::geometry_attribute::PointAttribute> {
None
}
}
pub struct PointCloudEncoder {
point_cloud: Option<PointCloud>,
options: EncoderOptions,
encoded_point_cloud_info: Option<EncodedPointCloudInfo>,
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub struct EncodedPointCloudInfo {
pub encoding_method: i32,
pub bitstream_version: (u8, u8),
pub speed: i32,
pub num_encoded_points: usize,
pub attributes: Vec<EncodedAttributeInfo>,
}
impl GeometryEncoder for PointCloudEncoder {
fn point_cloud(&self) -> Option<&PointCloud> {
self.point_cloud.as_ref()
}
fn mesh(&self) -> Option<&Mesh> {
None
}
fn corner_table(&self) -> Option<&CornerTable> {
None
}
fn options(&self) -> &EncoderOptions {
&self.options
}
fn get_geometry_type(&self) -> EncodedGeometryType {
EncodedGeometryType::PointCloud
}
}
impl Default for PointCloudEncoder {
fn default() -> Self {
Self::new()
}
}
impl PointCloudEncoder {
pub fn new() -> Self {
Self {
point_cloud: None,
options: EncoderOptions::default(),
encoded_point_cloud_info: None,
}
}
pub fn point_cloud(&self) -> Option<&PointCloud> {
self.point_cloud.as_ref()
}
pub fn encoded_point_cloud_info(&self) -> Option<&EncodedPointCloudInfo> {
self.encoded_point_cloud_info.as_ref()
}
pub fn set_point_cloud(&mut self, pc: PointCloud) {
self.point_cloud = Some(pc);
}
pub fn encode(&mut self, options: &EncoderOptions, out_buffer: &mut EncoderBuffer) -> Status {
self.options = options.clone();
self.encoded_point_cloud_info = None;
if self.point_cloud.is_none() {
return Err(DracoError::general("Point cloud not set".to_string()));
}
let pc = self.point_cloud.as_ref().unwrap();
validate_encodable_attributes(pc)?;
let method = select_encoding_method(pc, &self.options)?;
let (major, minor) = self.options.get_version();
let target = if method == 1 {
crate::version::EncodeTarget::PointCloudKdTree
} else {
crate::version::EncodeTarget::PointCloudSequential
};
crate::version::validate_encodable_version(major, minor, target)?;
let attributes = self.encode_geometry(out_buffer, method)?;
let (mut major, mut minor) = self.options.get_version();
if major == 0 && minor == 0 {
(major, minor) = DEFAULT_POINT_CLOUD_VERSION;
}
self.encoded_point_cloud_info = Some(EncodedPointCloudInfo {
encoding_method: method,
bitstream_version: (major, minor),
speed: self.options.get_speed(),
num_encoded_points: self
.point_cloud
.as_ref()
.expect("point cloud set")
.num_points(),
attributes,
});
Ok(())
}
fn encode_geometry(
&mut self,
out_buffer: &mut EncoderBuffer,
method: i32,
) -> Result<Vec<EncodedAttributeInfo>, DracoError> {
let pc = self.point_cloud.as_ref().expect("point cloud set");
self.encode_header(out_buffer, method)?;
self.encode_metadata(out_buffer)?;
if method == 1 {
out_buffer.encode_u32(pc.num_points() as u32);
if pc.num_attributes() == 0 {
out_buffer.encode_u8(0);
return Ok(Vec::new());
}
let mut att_encoder = KdTreeAttributesEncoder::new(0);
for i in 1..pc.num_attributes() {
att_encoder.add_attribute_id(i);
}
out_buffer.encode_u8(1);
att_encoder
.transform_attributes_to_portable_format(pc, &self.options)
.map_err(|err| err.context("Failed to transform attributes"))?;
att_encoder
.encode_attributes_encoder_data(pc, out_buffer)
.map_err(|err| err.context("Failed to encode attribute metadata"))?;
att_encoder
.encode_attributes(pc, &self.options, out_buffer)
.map_err(|err| err.context("Failed to encode attributes"))?;
att_encoder
.encode_data_needed_by_portable_transforms(out_buffer)
.map_err(|err| err.context("Failed to encode attribute transform data"))?;
} else {
let num_points = pc.num_points();
let num_attributes = pc.num_attributes();
let point_ids: Vec<PointIndex> = point_order(pc, &self.options);
out_buffer.encode_u32(num_points as u32);
if num_attributes == 0 {
out_buffer.encode_u8(0);
return Ok(Vec::new());
}
out_buffer.encode_u8(1);
let major = out_buffer.version_major();
let minor = out_buffer.version_minor();
if !uses_varint_encoding(major, minor) {
out_buffer.encode_u32(num_attributes as u32);
} else {
out_buffer.encode_varint(num_attributes as u64);
}
for i in 0..num_attributes {
let att = pc.attribute(i);
out_buffer.encode_u8(att.attribute_type() as u8);
out_buffer.encode_u8(att.data_type() as u8);
out_buffer.encode_u8(att.num_components());
out_buffer.encode_u8(if att.normalized() { 1 } else { 0 });
if !uses_varint_unique_id(major, minor) {
out_buffer.encode_u16(att.unique_id() as u16);
} else {
out_buffer.encode_varint(att.unique_id() as u64);
}
}
let encoder_types: Vec<SequentialAttributeEncoderType> = (0..num_attributes)
.map(|i| {
let quantization_bits =
self.options.get_attribute_int(i, "quantization_bits", -1);
select_sequential_encoder(pc.attribute(i), quantization_bits)
})
.collect();
for &encoder_type in &encoder_types {
out_buffer.encode_u8(encoder_type as u8);
}
let mut integer_encoders: Vec<Option<SequentialIntegerAttributeEncoder>> =
Vec::with_capacity(num_attributes as usize);
let mut normal_encoders: Vec<Option<SequentialNormalAttributeEncoder>> =
Vec::with_capacity(num_attributes as usize);
for i in 0..num_attributes {
let att = pc.attribute(i);
match encoder_types[i as usize] {
SequentialAttributeEncoderType::Normals => {
let mut att_encoder = SequentialNormalAttributeEncoder::new();
att_encoder.init(pc, i, &self.options).map_err(|e| {
DracoError::general(format!(
"Failed to init normal attribute encoder {i}: {e}"
))
})?;
att_encoder.encode_values(
pc,
&point_ids,
out_buffer,
&self.options,
self,
)?;
integer_encoders.push(None);
normal_encoders.push(Some(att_encoder));
continue;
}
SequentialAttributeEncoderType::Quantization
| SequentialAttributeEncoderType::Integer => {
let mut att_encoder = SequentialIntegerAttributeEncoder::new();
att_encoder.init(i);
att_encoder.encode_values(
pc,
&point_ids,
out_buffer,
&self.options,
self,
None,
false,
)?;
integer_encoders.push(Some(att_encoder));
}
SequentialAttributeEncoderType::Generic => {
let entry_size = att.byte_stride() as usize;
let data = att.buffer().data();
for &point_id in &point_ids {
let value_index = att.mapped_index(point_id).0 as usize;
let offset = value_index.checked_mul(entry_size).ok_or_else(|| {
DracoError::general(
"Point cloud raw attribute offset overflow".to_string(),
)
})?;
let end = offset.checked_add(entry_size).ok_or_else(|| {
DracoError::general(
"Point cloud raw attribute byte range overflow".to_string(),
)
})?;
if end > data.len() {
return Err(DracoError::general(
"Point cloud raw attribute data out of bounds".to_string(),
));
}
out_buffer.encode_data(&data[offset..end]);
}
integer_encoders.push(None);
}
}
normal_encoders.push(None);
}
for i in 0..num_attributes as usize {
if encoder_types[i] == SequentialAttributeEncoderType::Normals {
if let Some(ref att_encoder) = normal_encoders[i] {
let (major, minor) = self.options.get_version();
let bitstream_version = crate::version::bitstream_version(major, minor);
if bitstream_version != 0 && bitstream_version < 0x0102 {
continue;
}
att_encoder
.encode_data_needed_by_portable_transform(out_buffer)
.map_err(|err| {
DracoError::general(format!(
"Failed to encode normal attribute transform data {i}: {err}"
))
})?;
}
} else if let Some(ref att_encoder) = integer_encoders[i] {
att_encoder
.encode_data_needed_by_portable_transform(out_buffer)
.map_err(|err| {
DracoError::general(format!(
"Failed to encode quantization transform data {i}: {err}"
))
})?;
}
}
let mut attributes = Vec::with_capacity(num_attributes as usize);
for i in 0..num_attributes {
let att = pc.attribute(i);
let encoder_type = encoder_types[i as usize];
let prediction = match encoder_type {
SequentialAttributeEncoderType::Normals => normal_encoders[i as usize]
.as_ref()
.and_then(|encoder| encoder.selected_prediction()),
_ => integer_encoders[i as usize]
.as_ref()
.and_then(|encoder| encoder.selected_prediction()),
};
let quantization_bits = match encoder_type {
SequentialAttributeEncoderType::Quantization
| SequentialAttributeEncoderType::Normals => {
Some(self.options.get_attribute_int(i, "quantization_bits", -1))
}
SequentialAttributeEncoderType::Integer
| SequentialAttributeEncoderType::Generic => None,
};
attributes.push(EncodedAttributeInfo {
source_attribute_id: i,
attribute_type: att.attribute_type(),
data_type: att.data_type(),
num_components: att.num_components(),
normalized: att.normalized(),
unique_id: att.unique_id(),
num_encoded_values: att.size(),
encoder_type,
quantization_bits,
prediction,
position_min: None,
position_max: None,
});
}
return Ok(attributes);
}
Ok(Vec::new())
}
fn encode_metadata(&self, buffer: &mut EncoderBuffer) -> Status {
if let Some(metadata) = self
.point_cloud
.as_ref()
.and_then(|point_cloud| point_cloud.metadata())
.filter(|metadata| !metadata.is_empty())
{
metadata.encode(buffer)?;
}
Ok(())
}
fn encode_header(&self, buffer: &mut EncoderBuffer, method: i32) -> Status {
let (mut major, mut minor) = self.options.get_version();
if major == 0 && minor == 0 {
(major, minor) = DEFAULT_POINT_CLOUD_VERSION;
}
let has_metadata = self
.point_cloud
.as_ref()
.and_then(|point_cloud| point_cloud.metadata())
.is_some_and(|metadata| !metadata.is_empty());
if has_metadata && !has_header_flags(major, minor) {
return Err(DracoError::unsupported_version(
"Metadata requires Draco bitstream version 1.3 or newer".to_string(),
));
}
#[cfg(not(feature = "legacy_bitstream_encode"))]
match self.options.get_prediction_scheme() {
2 | 3 => {
return Err(DracoError::unsupported_feature(
"legacy prediction schemes require the legacy_bitstream_encode feature"
.to_string(),
));
}
_ => {}
}
buffer.encode_data(b"DRACO");
buffer.encode_u8(major);
buffer.encode_u8(minor);
buffer.set_version(major, minor);
buffer.encode_u8(self.get_geometry_type() as u8);
buffer.encode_u8(method as u8);
let flags = if has_metadata { METADATA_FLAG_MASK } else { 0 };
buffer.encode_u16(flags);
Ok(())
}
pub fn get_geometry_type(&self) -> EncodedGeometryType {
EncodedGeometryType::PointCloud
}
}
#[cfg(test)]
mod curve_grid_tests {
use super::curve_axis_bits;
use crate::encoder_options::EncoderOptions;
#[test]
fn the_grid_follows_the_positions_quantization() {
let mut options = EncoderOptions::new();
for bits in [4, 8, 14, 16, 21] {
options.set_attribute_int(0, "quantization_bits", bits);
assert_eq!(curve_axis_bits(&options, 0), bits as u32);
}
options.set_attribute_int(0, "quantization_bits", 30);
assert_eq!(curve_axis_bits(&options, 0), 21);
let options = EncoderOptions::new();
assert_eq!(curve_axis_bits(&options, 0), 21);
}
}