Skip to main content

draco_oxide/encode/attribute/
mod.rs

1pub(crate) mod attribute_encoder;
2pub(crate) mod portabilization;
3pub mod prediction_metadata;
4pub(crate) mod prediction_transform;
5
6use crate::encode::attribute::portabilization::PortabilizationType;
7pub use crate::encode::attribute::portabilization::Quantization;
8pub use crate::encode::attribute::prediction_transform::PredictionTransformType;
9
10use std::collections::HashMap;
11
12use draco_oxide_core::attribute::{Attribute, AttributeDomain, AttributeType, ComponentDataType};
13use draco_oxide_core::bit_coder::ByteWriter;
14use draco_oxide_core::codec::attribute::prediction_scheme::PredictionSchemeType;
15use draco_oxide_core::codec::attribute::sequence::PredictionDegreeTraverser;
16use draco_oxide_core::codec::connectivity::edgebreaker::TraversalType;
17use draco_oxide_core::codec::header::EncoderMethod;
18use draco_oxide_core::mesh::ds::AttributeDS;
19use draco_oxide_core::types::{ConfigType, CornerIdx};
20use draco_oxide_core::utils::bit_coder::leb128_write;
21
22use attribute_encoder::{SequenceSource, Sequencing};
23
24pub fn encode_attributes<W>(
25    adss: Vec<AttributeDS>,
26    // Corners of the edgebreaker traversal, produced by connectivity encoding and used to seed
27    // each attribute's sequencing.
28    corners_of_edgebreaker: Vec<CornerIdx>,
29    writer: &mut W,
30    cfg: &super::Config,
31) -> Result<(), Err>
32where
33    W: ByteWriter,
34{
35    match cfg.connectivity.encoder_method() {
36        EncoderMethod::Edgebreaker => {
37            encode_traversed_attributes(adss, corners_of_edgebreaker, writer, cfg)
38        }
39        EncoderMethod::Sequential => encode_linear_attributes(adss, writer, cfg),
40    }
41}
42
43/// Encodes each attribute over a traversal of its own connectivity, one
44/// attribute encoder per attribute.
45fn encode_traversed_attributes<W>(
46    adss: Vec<AttributeDS>,
47    corners_of_edgebreaker: Vec<CornerIdx>,
48    writer: &mut W,
49    cfg: &super::Config,
50) -> Result<(), Err>
51where
52    W: ByteWriter,
53{
54    // Write the number of attribute encoders/decoders (In draco-oxide, this is the same as the number of attributes as
55    // each attribute has its own encoder/decoder)
56    writer.write_u8(adss.len() as u8);
57
58    // The resolved traversal method per attribute. Prediction-degree traversal
59    // is defined over the position connectivity only, so an attribute with
60    // interior seams always walks depth-first.
61    let traversals: Vec<TraversalType> = adss
62        .iter()
63        .map(|att| {
64            if att.corner_table().has_interior_seams() {
65                TraversalType::DepthFirst
66            } else {
67                cfg.attribute
68                    .traversal_for(att.att_data().get_attribute_type())
69            }
70        })
71        .collect();
72
73    for (i, att) in adss.iter().enumerate() {
74        // encode decoder id
75        writer.write_u8((i as u8).wrapping_sub(1));
76        // Element type: a corner attribute without interior seams shares the
77        // position connectivity, so it is written as a vertex attribute,
78        // matching Google's encoder.
79        let domain = att.att_data().get_domain();
80        let wire_domain =
81            if domain == AttributeDomain::Corner && !att.corner_table().has_interior_seams() {
82                AttributeDomain::Position
83            } else {
84                domain
85            };
86        wire_domain.write_to(writer);
87        // write traversal method for attribute encoding/decoding sequencer.
88        traversals[i].write_to(writer);
89    }
90
91    let mut port_atts: Vec<Attribute> = Vec::new();
92    for att in &adss {
93        // Write 1 to indicate that the encoder is for one attribute.
94        writer.write_u8(1);
95
96        att.att_data().get_attribute_type().write_to(writer);
97        att.att_data().get_component_type().write_to(writer);
98        writer.write_u8(att.att_data().get_num_components() as u8);
99        writer.write_u8(0); // Normalized flag, currently not used.
100        writer.write_u8(att.att_data().get_id().as_usize() as u8); // unique id
101
102        // write the decoder type.
103        PortabilizationType::default_for(
104            att.att_data().get_attribute_type(),
105            att.att_data().get_component_type(),
106        )
107        .write_to(writer);
108    }
109
110    // `adss` is built one-per-attribute and in the same order as `atts`, so each attribute is
111    // paired with its own attribute data structure here.
112    //
113    // Attributes without interior seams share the position connectivity, so
114    // attributes walking it with the same traversal method have identical
115    // sequences. Mirroring the decoder, the first attribute of each method
116    // records the walk and later attributes replay the recording borrowed;
117    // an attribute with its own connectivity walks lazily inside its encoder.
118    // Prediction-degree traversal has no lazy walk and is materialized up
119    // front.
120    let mut shared_sequences: Vec<(TraversalType, Vec<CornerIdx>)> = Vec::new();
121    for (ads, traversal) in adss.into_iter().zip(traversals) {
122        let parents_ids = ads.att_data().get_parents();
123        let parents = parents_ids
124            .iter()
125            .map(|id| port_atts.iter().find(|att| att.get_id() == *id).unwrap())
126            .collect::<Vec<_>>();
127
128        let sequence = if ads.corner_table().has_interior_seams() {
129            SequenceSource::Own
130        } else {
131            match shared_sequences.iter().position(|(t, _)| *t == traversal) {
132                Some(i) => SequenceSource::Shared(&shared_sequences[i].1),
133                None => match traversal {
134                    TraversalType::DepthFirst => {
135                        shared_sequences.push((traversal, Vec::new()));
136                        SequenceSource::Record(&mut shared_sequences.last_mut().unwrap().1)
137                    }
138                    TraversalType::PredictionDegree => {
139                        let s =
140                            PredictionDegreeTraverser::new(&ads, corners_of_edgebreaker.clone())
141                                .compute_seqeunce();
142                        shared_sequences.push((traversal, s));
143                        SequenceSource::Shared(&shared_sequences.last().unwrap().1)
144                    }
145                },
146            }
147        };
148
149        let ty = ads.att_data().get_attribute_type();
150        let component_ty = ads.att_data().get_component_type();
151        let encoder = attribute_encoder::AttributeEncoder::new(
152            ads,
153            &parents,
154            &corners_of_edgebreaker,
155            writer,
156            cfg.attribute.encoder_config_for(ty, component_ty),
157            Sequencing::Traversal,
158            sequence,
159        );
160
161        // This encoder carries one attribute, so its portabilization metadata
162        // belongs immediately after its payload.
163        let (port_att, port_info) = encoder.encode::<true>()?;
164        port_atts.push(port_att);
165        for byte in port_info {
166            writer.write_u8(byte);
167        }
168    }
169
170    Ok(())
171}
172
173/// Encodes every attribute over the point space in index order, in a single
174/// attribute encoder. Matches Google's sequential encoder, which has no corner
175/// table to sequence or predict over.
176fn encode_linear_attributes<W>(
177    adss: Vec<AttributeDS>,
178    writer: &mut W,
179    cfg: &super::Config,
180) -> Result<(), Err>
181where
182    W: ByteWriter,
183{
184    // One attribute encoder carries every attribute.
185    writer.write_u8(1);
186
187    leb128_write(adss.len() as u64, writer);
188    for ads in &adss {
189        let att = ads.att_data();
190        att.get_attribute_type().write_to(writer);
191        att.get_component_type().write_to(writer);
192        writer.write_u8(att.get_num_components() as u8);
193        writer.write_u8(0); // Normalized flag, currently not used.
194        leb128_write(att.get_id().as_usize() as u64, writer);
195    }
196    for ads in &adss {
197        PortabilizationType::default_for(
198            ads.att_data().get_attribute_type(),
199            ads.att_data().get_component_type(),
200        )
201        .write_to(writer);
202    }
203
204    let num_points = adss[0].global_ds().num_points();
205    let mut port_infos = Vec::with_capacity(adss.len());
206    for ads in adss {
207        let ty = ads.att_data().get_attribute_type();
208        let component_ty = ads.att_data().get_component_type();
209        let encoder = attribute_encoder::AttributeEncoder::new(
210            ads,
211            &[],
212            &[],
213            writer,
214            cfg.attribute
215                .encoder_config_for(ty, component_ty)
216                .for_sequential(),
217            Sequencing::Linear { num_points },
218            attribute_encoder::SequenceSource::Own,
219        );
220        port_infos.push(encoder.encode::<true>()?.1);
221    }
222
223    // The encoder emits every payload before the first portabilization block.
224    for byte in port_infos.into_iter().flatten() {
225        writer.write_u8(byte);
226    }
227
228    Ok(())
229}
230
231/// Per-attribute encoding configuration, keyed by attribute type. Any type
232/// without an explicit override falls back to the built-in `default_for(ty, len)`
233/// behaviour, so `Config::default()` encodes every attribute with the built-in
234/// defaults for its type.
235#[derive(Clone, Debug)]
236pub struct Config {
237    overrides: HashMap<AttributeType, AttributeConfig>,
238}
239
240/// Per-attribute-type encoding overrides. Every knob is optional; a `None` field
241/// keeps the built-in default for that attribute type, so a bare
242/// `AttributeConfig::default()` is a no-op. Invalid combinations (e.g. a texture
243/// predictor on a normal attribute) are representable here and rejected by
244/// [`Config::validate`](crate::encode::Config::validate).
245#[derive(Clone, Debug, Default)]
246pub struct AttributeConfig {
247    /// Prediction scheme override.
248    pub prediction: Option<PredictionSchemeType>,
249    /// Prediction transform override.
250    pub transform: Option<PredictionTransformType>,
251    /// Quantization resolution override.
252    pub quantization: Option<Quantization>,
253    /// Normal-specific encoding mode override (only valid for `Normal`).
254    pub normal_encoding: Option<NormalEncoding>,
255    /// Traversal method override. Applies only under edgebreaker connectivity,
256    /// and only to attributes without interior seams; everything else walks
257    /// depth-first.
258    pub traversal: Option<TraversalType>,
259}
260
261/// How a normal attribute is compressed.
262#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, serde::Deserialize)]
263pub enum NormalEncoding {
264    /// Octahedrally quantize the input normals and encode the real octahedral
265    /// corrections (the default, lossy-by-quantization path).
266    #[default]
267    Quantized,
268    /// Zero-CPU: trust the decoder's geometry-derived prediction and emit an
269    /// all-zero correction stream. The input normal values are ignored, and
270    /// only their seams are used.
271    PredictedOnly,
272}
273
274impl ConfigType for Config {
275    fn default() -> Self {
276        Self {
277            overrides: HashMap::new(),
278        }
279    }
280}
281
282impl Config {
283    /// Overrides how normal attributes are compressed.
284    pub fn set_normal_encoding(&mut self, enc: NormalEncoding) {
285        self.overrides
286            .entry(AttributeType::Normal)
287            .or_default()
288            .normal_encoding = Some(enc);
289    }
290
291    /// Overrides the per-type encoding for `ty`, replacing any prior override.
292    pub fn set(&mut self, ty: AttributeType, cfg: AttributeConfig) {
293        self.overrides.insert(ty, cfg);
294    }
295
296    /// The current override for `ty` (a clone), or an empty default if none is
297    /// set. Useful for read-modify-write layering (e.g. a CLI flag patching a
298    /// single knob on top of a file-loaded config).
299    pub fn get(&self, ty: AttributeType) -> AttributeConfig {
300        self.overrides.get(&ty).cloned().unwrap_or_default()
301    }
302
303    /// The per-type overrides, for validation.
304    pub(crate) fn overrides(&self) -> &HashMap<AttributeType, AttributeConfig> {
305        &self.overrides
306    }
307
308    /// Resolves the traversal method for an attribute of type `ty`.
309    fn traversal_for(&self, ty: AttributeType) -> TraversalType {
310        self.overrides
311            .get(&ty)
312            .and_then(|o| o.traversal)
313            .unwrap_or(TraversalType::DepthFirst)
314    }
315
316    /// Resolves the per-attribute encoder config for an attribute of type `ty`,
317    /// honoring any override and otherwise falling back to the built-in default.
318    fn encoder_config_for(
319        &self,
320        ty: AttributeType,
321        component_ty: ComponentDataType,
322    ) -> attribute_encoder::Config {
323        let Some(over) = self.overrides.get(&ty) else {
324            return attribute_encoder::Config::default_for(ty, component_ty);
325        };
326
327        // Start from the zero-correction base for PredictedOnly normals, else the
328        // regular per-type default; then patch in any explicit knobs.
329        let mut base = if over.normal_encoding == Some(NormalEncoding::PredictedOnly) {
330            attribute_encoder::Config::predicted_normals()
331        } else {
332            attribute_encoder::Config::default_for(ty, component_ty)
333        };
334
335        if let Some(scheme) = &over.prediction {
336            base.set_prediction_scheme(scheme.clone());
337        }
338        if let Some(transform) = over.transform {
339            base.set_prediction_transform(transform);
340        }
341        if let Some(quant) = over.quantization {
342            base.set_quantization(quant);
343        }
344        base
345    }
346}
347
348/// Errors from attribute encoding.
349#[remain::sorted]
350#[derive(thiserror::Error, Debug)]
351pub enum Err {
352    /// Encoding of a single attribute failed.
353    #[error("Attribute encoding error: {0}")]
354    AttributeError(#[from] attribute_encoder::Err),
355}