draco-oxide 0.1.0-alpha.11

draco-oxide is a rust rewrite of Google's draco mesh compression library.
Documentation
//! TOML/serde-facing view of [`Config`](super::Config).
//!
//! Deserializing a `Config` routes through [`ConfigSpec`], a flat, all-optional
//! spec that goes through the public builder methods, keeping the real config's
//! private fields and internal representation off the deserialization surface.
//! Core codec enums are mirrored by small local `*Name` enums here so that
//! `draco-oxide-core` stays free of a serde dependency. Grow this alongside the
//! builder methods.
//!
//! ```toml
//! metadata = false
//! connectivity = "Edgebreaker"
//!
//! [edgebreaker]
//! traversal = "Valence"
//!
//! [sequential]
//! indices = "Direct"                   # or "Compressed"
//!
//! [attributes.Position]
//! prediction   = "MeshParallelogramPrediction"
//! quantization = { bits = 14 }        # or { max_error = 0.001 }
//!
//! [attributes.Normal]
//! encoding     = "PredictedOnly"
//! quantization = { bits = 8 }
//! ```

use std::collections::HashMap;

use serde::Deserialize;

use draco_oxide_core::attribute::AttributeType;
use draco_oxide_core::codec::attribute::prediction_scheme::PredictionSchemeType;
use draco_oxide_core::codec::connectivity::edgebreaker::{EdgebreakerKind, TraversalType};
use draco_oxide_core::codec::connectivity::sequential::Method as SequentialMethod;
use draco_oxide_core::types::ConfigType;

use super::attribute::{AttributeConfig, NormalEncoding, PredictionTransformType, Quantization};
use super::{Config, EdgebreakerConfig, SequentialConfig};

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
pub(super) struct ConfigSpec {
    /// Back-compat shortcut equivalent to `[attributes.Normal] encoding = …`.
    normal: NormalEncoding,
    metadata: bool,
    connectivity: ConnectivityName,
    edgebreaker: EdgebreakerSpec,
    sequential: SequentialSpec,
    attributes: HashMap<AttributeName, AttributeConfigSpec>,
}

impl From<ConfigSpec> for Config {
    fn from(spec: ConfigSpec) -> Self {
        let mut cfg = <Config as ConfigType>::default()
            .with_metadata(spec.metadata)
            // Applied first so an explicit `[attributes.Normal]` below wins.
            .with_normals(spec.normal);

        cfg = match spec.connectivity {
            ConnectivityName::Edgebreaker => cfg.with_edgebreaker(EdgebreakerConfig {
                traversal: spec.edgebreaker.traversal.into(),
            }),
            ConnectivityName::Sequential => cfg.with_sequential(SequentialConfig {
                encoder_method: spec.sequential.indices.into(),
            }),
        };

        for (name, aspec) in spec.attributes {
            cfg = cfg.with_attribute(name.into(), aspec.into());
        }

        cfg
    }
}

#[derive(Debug, Clone, Copy, Default, Deserialize)]
enum ConnectivityName {
    #[default]
    Edgebreaker,
    Sequential,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct EdgebreakerSpec {
    traversal: TraversalName,
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct SequentialSpec {
    indices: IndexStorageName,
}

#[derive(Debug, Clone, Copy, Default, Deserialize)]
enum IndexStorageName {
    #[default]
    Direct,
    Compressed,
}

impl From<IndexStorageName> for SequentialMethod {
    fn from(i: IndexStorageName) -> Self {
        match i {
            IndexStorageName::Direct => SequentialMethod::DirectIndices,
            IndexStorageName::Compressed => SequentialMethod::Compressed,
        }
    }
}

#[derive(Debug, Clone, Copy, Default, Deserialize)]
enum TraversalName {
    Standard,
    Predictive,
    #[default]
    Valence,
}

impl From<TraversalName> for EdgebreakerKind {
    fn from(t: TraversalName) -> Self {
        match t {
            TraversalName::Standard => EdgebreakerKind::Standard,
            TraversalName::Predictive => EdgebreakerKind::Predictive,
            TraversalName::Valence => EdgebreakerKind::Valence,
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Deserialize)]
enum AttributeName {
    Position,
    Normal,
    Color,
    TextureCoordinate,
    Custom,
    Tangent,
    Material,
    Joint,
    Weight,
}

impl From<AttributeName> for AttributeType {
    fn from(n: AttributeName) -> Self {
        match n {
            AttributeName::Position => AttributeType::Position,
            AttributeName::Normal => AttributeType::Normal,
            AttributeName::Color => AttributeType::Color,
            AttributeName::TextureCoordinate => AttributeType::TextureCoordinate,
            AttributeName::Custom => AttributeType::Custom,
            AttributeName::Tangent => AttributeType::Tangent,
            AttributeName::Material => AttributeType::Material,
            AttributeName::Joint => AttributeType::Joint,
            AttributeName::Weight => AttributeType::Weight,
        }
    }
}

#[derive(Debug, Clone, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct AttributeConfigSpec {
    prediction: Option<SchemeName>,
    transform: Option<TransformName>,
    quantization: Option<QuantizationSpec>,
    encoding: Option<NormalEncoding>,
    traversal: Option<AttributeTraversalName>,
}

impl From<AttributeConfigSpec> for AttributeConfig {
    fn from(s: AttributeConfigSpec) -> Self {
        AttributeConfig {
            prediction: s.prediction.map(Into::into),
            transform: s.transform.map(Into::into),
            quantization: s.quantization.map(Into::into),
            normal_encoding: s.encoding,
            traversal: s.traversal.map(Into::into),
        }
    }
}

#[derive(Debug, Clone, Copy, Deserialize)]
enum SchemeName {
    DeltaPrediction,
    MeshConstrainedMultiParallelogramPrediction,
    MeshParallelogramPrediction,
    MeshNormalPrediction,
    MeshPredictionForTextureCoordinates,
    NoPrediction,
}

impl From<SchemeName> for PredictionSchemeType {
    fn from(s: SchemeName) -> Self {
        match s {
            SchemeName::DeltaPrediction => PredictionSchemeType::DeltaPrediction,
            SchemeName::MeshConstrainedMultiParallelogramPrediction => {
                PredictionSchemeType::MeshConstrainedMultiParallelogramPrediction
            }
            SchemeName::MeshParallelogramPrediction => {
                PredictionSchemeType::MeshParallelogramPrediction
            }
            SchemeName::MeshNormalPrediction => PredictionSchemeType::MeshNormalPrediction,
            SchemeName::MeshPredictionForTextureCoordinates => {
                PredictionSchemeType::MeshPredictionForTextureCoordinates
            }
            SchemeName::NoPrediction => PredictionSchemeType::NoPrediction,
        }
    }
}

/// TOML form of the per-attribute [`TraversalType`], distinct from the
/// edgebreaker connectivity `traversal` key.
#[derive(Debug, Clone, Copy, Deserialize)]
enum AttributeTraversalName {
    DepthFirst,
    PredictionDegree,
}

impl From<AttributeTraversalName> for TraversalType {
    fn from(t: AttributeTraversalName) -> Self {
        match t {
            AttributeTraversalName::DepthFirst => TraversalType::DepthFirst,
            AttributeTraversalName::PredictionDegree => TraversalType::PredictionDegree,
        }
    }
}

#[derive(Debug, Clone, Copy, Deserialize)]
enum TransformName {
    NoTransform,
    Difference,
    WrappedDifference,
    OctahedralOrthogonal,
}

impl From<TransformName> for PredictionTransformType {
    fn from(t: TransformName) -> Self {
        match t {
            TransformName::NoTransform => PredictionTransformType::NoTransform,
            TransformName::Difference => PredictionTransformType::Difference,
            TransformName::WrappedDifference => PredictionTransformType::WrappedDifference,
            TransformName::OctahedralOrthogonal => PredictionTransformType::OctahedralOrthogonal,
        }
    }
}

/// TOML form of [`Quantization`]: `{ bits = N }`, `{ max_error = E }` (against
/// the observed value range), or `{ max_error = E, range = R }` (against a
/// caller-supplied domain span). `bits` takes precedence if several are
/// present; an empty table falls back to the attribute's default resolution.
#[derive(Debug, Clone, Copy, Default, Deserialize)]
#[serde(default, deny_unknown_fields)]
struct QuantizationSpec {
    bits: Option<u8>,
    max_error: Option<f32>,
    range: Option<f32>,
}

impl From<QuantizationSpec> for Quantization {
    fn from(q: QuantizationSpec) -> Self {
        match (q.bits, q.max_error, q.range) {
            (Some(bits), _, _) => Quantization::Bits(bits),
            (None, Some(max_error), Some(range)) => Quantization::Bounded { range, max_error },
            (None, Some(max_error), None) => Quantization::MaxError(max_error),
            (None, None, _) => Quantization::default(),
        }
    }
}