use crate::compression_config::EncodedGeometryType;
use crate::draco_types::DataType;
use crate::encoder_buffer::EncoderBuffer;
use crate::encoder_options::EncoderOptions;
use crate::geometry_attribute::PointAttribute;
use crate::geometry_indices::PointIndex;
use crate::kd_tree_attributes_encoder::KdTreeAttributesEncoder;
use crate::mesh::Mesh;
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 {
for att_id in 0..point_cloud.num_attributes() {
let attribute = point_cloud.attribute(att_id);
if attribute.num_components() == 0 {
return Err(DracoError::DracoError(format!(
"Attribute {att_id} has zero components and cannot be encoded"
)));
}
if attribute.data_type() == DataType::Invalid {
return Err(DracoError::DracoError(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::DracoError(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::DracoError(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::DracoError(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::DracoError(format!("Attribute {att_id} value extent overflows"))
})?;
let available = attribute.buffer().data_size();
if available < required {
return Err(DracoError::DracoError(format!(
"Attribute {att_id} needs {required} bytes for {num_values} values but its buffer \
holds {available}"
)));
}
Ok(())
}
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::DracoError(
"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,
}
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(),
}
}
pub fn point_cloud(&self) -> Option<&PointCloud> {
self.point_cloud.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();
if self.point_cloud.is_none() {
return Err(DracoError::DracoError("Point cloud not set".to_string()));
}
let pc = self.point_cloud.as_ref().unwrap();
validate_encodable_attributes(pc)?;
let (major, minor) = self.options.get_version();
crate::version::validate_encodable_version(major, minor, DEFAULT_POINT_CLOUD_VERSION)?;
let method = select_encoding_method(pc, &self.options)?;
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(());
}
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);
if !att_encoder.transform_attributes_to_portable_format(pc, &self.options) {
return Err(DracoError::DracoError(
"Failed to transform attributes".to_string(),
));
}
if !att_encoder.encode_attributes_encoder_data(pc, out_buffer) {
return Err(DracoError::DracoError(
"Failed to encode attribute metadata".to_string(),
));
}
if !att_encoder.encode_attributes(pc, &self.options, out_buffer) {
return Err(DracoError::DracoError(
"Failed to encode attributes".to_string(),
));
}
if !att_encoder.encode_data_needed_by_portable_transforms(out_buffer) {
return Err(DracoError::DracoError(
"Failed to encode attribute transform data".to_string(),
));
}
} else {
let num_points = pc.num_points();
let num_attributes = pc.num_attributes();
let point_ids: Vec<PointIndex> =
(0..num_points).map(|i| PointIndex(i as u32)).collect();
out_buffer.encode_u32(num_points as u32);
if num_attributes == 0 {
out_buffer.encode_u8(0);
return Ok(());
}
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();
if !att_encoder.init(pc, i, &self.options) {
return Err(DracoError::DracoError(format!(
"Failed to init normal attribute encoder {}",
i
)));
}
if !att_encoder.encode_values(
pc,
&point_ids,
out_buffer,
&self.options,
self,
) {
return Err(DracoError::DracoError(format!(
"Failed to encode attribute {}",
i
)));
}
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);
if !att_encoder.encode_values(
pc,
&point_ids,
out_buffer,
&self.options,
self,
None,
false,
) {
return Err(DracoError::DracoError(format!(
"Failed to encode attribute {}",
i
)));
}
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::DracoError(
"Point cloud raw attribute offset overflow".to_string(),
)
})?;
let end = offset.checked_add(entry_size).ok_or_else(|| {
DracoError::DracoError(
"Point cloud raw attribute byte range overflow".to_string(),
)
})?;
if end > data.len() {
return Err(DracoError::DracoError(
"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;
}
if !att_encoder.encode_data_needed_by_portable_transform(out_buffer) {
return Err(DracoError::DracoError(format!(
"Failed to encode normal attribute transform data {}",
i
)));
}
}
} else if let Some(ref att_encoder) = integer_encoders[i] {
if !att_encoder.encode_data_needed_by_portable_transform(out_buffer) {
return Err(DracoError::DracoError(format!(
"Failed to encode quantization transform data {}",
i
)));
}
}
}
}
Ok(())
}
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::UnsupportedVersion(
"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::UnsupportedFeature(
"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
}
}