Skip to main content

draco_oxide/encode/point_cloud/
mod.rs

1//! Point-cloud encoding (bitstream 2.3, kd-tree method).
2
3mod bit_encoder;
4mod kd_tree;
5
6use std::collections::HashMap;
7
8use draco_oxide_core::attribute::{Attribute, AttributeType, ComponentDataType};
9use draco_oxide_core::bit_coder::ByteWriter;
10use draco_oxide_core::point_cloud::PointCloud;
11use draco_oxide_core::types::{ConfigType, NdVector, PointIdx, Vector};
12use draco_oxide_core::utils::bit_coder::leb128_write;
13use thiserror::Error;
14
15use super::attribute::portabilization::Quantization;
16
17const GEOMETRY_TYPE_POINT_CLOUD: u8 = 0;
18const METHOD_KD_TREE: u8 = 1;
19const METADATA_FLAG_MASK: u16 = 0x8000;
20
21/// The highest kd-tree compression level the format defines.
22pub const MAX_COMPRESSION_LEVEL: u8 = 6;
23
24/// Errors returned while encoding a point cloud.
25#[remain::sorted]
26#[derive(Error, Debug)]
27#[non_exhaustive]
28pub enum Err {
29    /// The entropy coder failed.
30    #[error("entropy error: {0}")]
31    Entropy(#[from] crate::encode::entropy::rans::Err),
32    /// An attribute holds a value that is not finite.
33    #[error("attribute {0:?} holds a non-finite value")]
34    NonFiniteValue(AttributeType),
35    /// The point cloud has no points.
36    #[error("the point cloud has no points")]
37    NoPoints,
38    /// Quantization produced a value outside the range the kd-tree codes.
39    #[error("quantized value out of range for attribute {0:?}")]
40    QuantizedValueOutOfRange(AttributeType),
41    /// The attribute layout cannot be carried by a kd-tree stream.
42    #[error("unsupported attribute layout: {0} components of {1:?}")]
43    UnsupportedAttributeLayout(usize, ComponentDataType),
44    /// A component type the kd-tree method cannot carry.
45    #[error("unsupported component type for a point cloud: {0:?}")]
46    UnsupportedComponentType(ComponentDataType),
47    /// The configured compression level is outside `0..=6`.
48    #[error("unsupported kd-tree compression level: {0}")]
49    UnsupportedCompressionLevel(u8),
50}
51
52/// Point-cloud encoder configuration.
53#[derive(Clone, Debug)]
54pub struct Config {
55    compression_level: u8,
56    quantization: Quantization,
57    overrides: HashMap<AttributeType, Quantization>,
58    metadata: bool,
59}
60
61impl ConfigType for Config {
62    fn default() -> Self {
63        Self {
64            compression_level: MAX_COMPRESSION_LEVEL,
65            quantization: Quantization::Bits(11),
66            overrides: HashMap::new(),
67            metadata: false,
68        }
69    }
70}
71
72impl Config {
73    /// Sets the kd-tree compression level (`0..=6`). Level 6 also picks the
74    /// split axis adaptively.
75    pub fn with_compression_level(mut self, level: u8) -> Self {
76        self.compression_level = level;
77        self
78    }
79
80    /// Sets the quantization of every float attribute without an override.
81    pub fn with_quantization(mut self, quantization: Quantization) -> Self {
82        self.quantization = quantization;
83        self
84    }
85
86    /// Overrides the quantization of one attribute type.
87    pub fn with_attribute_quantization(
88        mut self,
89        att_type: AttributeType,
90        quantization: Quantization,
91    ) -> Self {
92        self.overrides.insert(att_type, quantization);
93        self
94    }
95
96    /// Writes the metadata section.
97    pub fn with_metadata(mut self, metadata: bool) -> Self {
98        self.metadata = metadata;
99        self
100    }
101
102    /// Rejects configurations that cannot be encoded.
103    pub fn validate(&self) -> Result<(), Err> {
104        if self.compression_level > MAX_COMPRESSION_LEVEL {
105            return Err(Err::UnsupportedCompressionLevel(self.compression_level));
106        }
107        Ok(())
108    }
109
110    fn quantization_for(&self, att_type: AttributeType) -> Quantization {
111        self.overrides
112            .get(&att_type)
113            .copied()
114            .unwrap_or(self.quantization)
115    }
116}
117
118/// What the decoder needs to undo one attribute's portabilization.
119enum Portable {
120    Quantized { min: Vec<f32>, range: f32, bits: u8 },
121    Unsigned,
122    Signed { mins: Vec<i32> },
123}
124
125/// Encodes a point cloud into the writer.
126pub(crate) fn encode_impl<W>(pc: PointCloud, writer: &mut W, cfg: Config) -> Result<(), Err>
127where
128    W: ByteWriter,
129{
130    cfg.validate()?;
131    let num_points = pc.num_points();
132    if num_points == 0 {
133        return Err(Err::NoPoints);
134    }
135
136    let attributes = pc.into_attributes();
137    let dimension: usize = attributes.iter().map(|a| a.get_num_components()).sum();
138
139    for b in b"DRACO" {
140        writer.write_u8(*b);
141    }
142    writer.write_u8(2);
143    writer.write_u8(3);
144    writer.write_u8(GEOMETRY_TYPE_POINT_CLOUD);
145    writer.write_u8(METHOD_KD_TREE);
146    let flags = if cfg.metadata { METADATA_FLAG_MASK } else { 0 };
147    writer.write_u16(flags);
148    if cfg.metadata {
149        super::metadata::encode_point_cloud_metadata(&attributes, writer);
150    }
151
152    writer.write_u32(num_points as u32);
153
154    writer.write_u8(1);
155    leb128_write(attributes.len() as u64, writer);
156    for (i, att) in attributes.iter().enumerate() {
157        att.get_attribute_type().write_to(writer);
158        att.get_component_type().write_to(writer);
159        writer.write_u8(att.get_num_components() as u8);
160        writer.write_u8(0);
161        leb128_write(i as u64, writer);
162    }
163
164    let mut points = vec![0u32; num_points * dimension];
165    let mut portables = Vec::with_capacity(attributes.len());
166    let mut offset = 0usize;
167    for att in &attributes {
168        let n = att.get_num_components();
169        portables.push(portabilize(
170            att,
171            &cfg,
172            num_points,
173            &mut points,
174            dimension,
175            offset,
176        )?);
177        offset += n;
178    }
179
180    writer.write_u8(cfg.compression_level);
181    kd_tree::encode_points(&mut points, dimension, cfg.compression_level, writer)?;
182
183    for portable in &portables {
184        if let Portable::Quantized { min, range, bits } = portable {
185            for m in min {
186                writer.write_u32(m.to_bits());
187            }
188            writer.write_u32(range.to_bits());
189            writer.write_u8(*bits);
190        }
191    }
192    for portable in &portables {
193        if let Portable::Signed { mins } = portable {
194            for &m in mins {
195                leb128_write(zigzag(m) as u64, writer);
196            }
197        }
198    }
199    Ok(())
200}
201
202/// Writes one attribute's columns into the point array as unsigned integers.
203fn portabilize(
204    att: &Attribute,
205    cfg: &Config,
206    num_points: usize,
207    points: &mut [u32],
208    dimension: usize,
209    offset: usize,
210) -> Result<Portable, Err> {
211    let num_components = att.get_num_components();
212    if !(1..=4).contains(&num_components) {
213        return Err(Err::UnsupportedAttributeLayout(
214            num_components,
215            att.get_component_type(),
216        ));
217    }
218    match num_components {
219        1 => portabilize_typed::<1>(att, cfg, num_points, points, dimension, offset),
220        2 => portabilize_typed::<2>(att, cfg, num_points, points, dimension, offset),
221        3 => portabilize_typed::<3>(att, cfg, num_points, points, dimension, offset),
222        _ => portabilize_typed::<4>(att, cfg, num_points, points, dimension, offset),
223    }
224}
225
226fn portabilize_typed<const N: usize>(
227    att: &Attribute,
228    cfg: &Config,
229    num_points: usize,
230    points: &mut [u32],
231    dimension: usize,
232    offset: usize,
233) -> Result<Portable, Err>
234where
235    NdVector<N, f32>: Vector<N, Component = f32>,
236    NdVector<N, u8>: Vector<N, Component = u8>,
237    NdVector<N, u16>: Vector<N, Component = u16>,
238    NdVector<N, u32>: Vector<N, Component = u32>,
239    NdVector<N, i8>: Vector<N, Component = i8>,
240    NdVector<N, i16>: Vector<N, Component = i16>,
241    NdVector<N, i32>: Vector<N, Component = i32>,
242{
243    let att_type = att.get_attribute_type();
244
245    macro_rules! write_signed {
246        ($ty:ty) => {{
247            let values: Vec<NdVector<N, $ty>> = (0..num_points)
248                .map(|p| att.get(PointIdx::from(p)))
249                .collect();
250            let mut mins = vec![i32::MAX; N];
251            for v in &values {
252                for c in 0..N {
253                    mins[c] = mins[c].min(*v.get(c) as i32);
254                }
255            }
256            for (p, v) in values.iter().enumerate() {
257                for c in 0..N {
258                    // Widest span is i32::MAX - i32::MIN: it overflows an i32
259                    // subtraction but still fits the u32 the kd-tree codes.
260                    points[p * dimension + offset + c] = (*v.get(c) as i64 - mins[c] as i64) as u32;
261                }
262            }
263            Ok(Portable::Signed { mins })
264        }};
265    }
266
267    macro_rules! write_unsigned {
268        ($ty:ty) => {{
269            for p in 0..num_points {
270                let v: NdVector<N, $ty> = att.get(PointIdx::from(p));
271                for c in 0..N {
272                    points[p * dimension + offset + c] = *v.get(c) as u32;
273                }
274            }
275            Ok(Portable::Unsigned)
276        }};
277    }
278
279    match att.get_component_type() {
280        ComponentDataType::F32 => {
281            let values: Vec<NdVector<N, f32>> = (0..num_points)
282                .map(|p| att.get(PointIdx::from(p)))
283                .collect();
284            let mut min = [f32::INFINITY; N];
285            let mut max = [f32::NEG_INFINITY; N];
286            for v in &values {
287                for c in 0..N {
288                    let x = *v.get(c);
289                    if !x.is_finite() {
290                        return Err(Err::NonFiniteValue(att_type));
291                    }
292                    min[c] = min[c].min(x);
293                    max[c] = max[c].max(x);
294                }
295            }
296            // One step is shared by every component, so the largest extent
297            // sets the resolution.
298            let mut range = 0.0f32;
299            for c in 0..N {
300                range = range.max(max[c] - min[c]);
301            }
302            if range == 0.0 {
303                range = 1.0;
304            }
305            let bits = cfg.quantization_for(att_type).resolve(range);
306            let max_quantized = (1u32 << bits) - 1;
307            let inverse_delta = max_quantized as f32 / range;
308            for (p, v) in values.iter().enumerate() {
309                for c in 0..N {
310                    let q = ((*v.get(c) - min[c]) * inverse_delta + 0.5).floor();
311                    if !(0.0..=max_quantized as f32).contains(&q) {
312                        return Err(Err::QuantizedValueOutOfRange(att_type));
313                    }
314                    points[p * dimension + offset + c] = q as u32;
315                }
316            }
317            Ok(Portable::Quantized {
318                min: min.to_vec(),
319                range,
320                bits,
321            })
322        }
323        ComponentDataType::U8 => write_unsigned!(u8),
324        ComponentDataType::U16 => write_unsigned!(u16),
325        ComponentDataType::U32 => write_unsigned!(u32),
326        ComponentDataType::I8 => write_signed!(i8),
327        ComponentDataType::I16 => write_signed!(i16),
328        ComponentDataType::I32 => write_signed!(i32),
329        other => Err(Err::UnsupportedComponentType(other)),
330    }
331}
332
333/// Maps a signed integer onto the unsigned code the varint carries.
334fn zigzag(v: i32) -> u32 {
335    ((v << 1) ^ (v >> 31)) as u32
336}