1pub(crate) mod attribute;
2pub(crate) mod config_spec;
3pub(crate) mod connectivity;
4pub(crate) mod ds;
5pub(crate) mod entropy;
6pub(crate) mod header;
7pub(crate) mod metadata;
8
9use draco_oxide_core::bit_coder::ByteWriter;
10use draco_oxide_core::debug_write;
11use draco_oxide_core::mesh::Mesh;
12use draco_oxide_core::types::ConfigType;
13use thiserror::Error;
14
15#[cfg(feature = "evaluation")]
16use crate::eval;
17
18pub trait EncoderConfig {
19 type Encoder;
20 fn get_encoder(&self) -> Self::Encoder;
21}
22
23pub use attribute::{AttributeConfig, NormalEncoding, Quantization};
24pub use connectivity::edgebreaker::Config as EdgebreakerConfig;
25pub use connectivity::sequential::Config as SequentialConfig;
26pub use connectivity::Config as ConnectivityConfig;
27
28use config_spec::ConfigSpec;
29
30#[derive(Debug, Clone, serde::Deserialize)]
31#[serde(from = "ConfigSpec")]
32pub struct Config {
33 connectivity: connectivity::Config,
36 attribute: attribute::Config,
38 geometry_type: header::EncodedGeometryType,
39 metadata: bool,
40}
41
42impl ConfigType for Config {
43 fn default() -> Self {
44 Self {
45 connectivity: connectivity::Config::default(),
46 attribute: attribute::Config::default(),
47 geometry_type: header::EncodedGeometryType::TrianglarMesh,
48 metadata: false,
49 }
50 }
51}
52
53impl Config {
54 pub fn with_normals(mut self, enc: NormalEncoding) -> Self {
67 self.attribute.set_normal_encoding(enc);
68 self
69 }
70
71 pub fn with_attribute(
75 mut self,
76 ty: draco_oxide_core::attribute::AttributeType,
77 cfg: AttributeConfig,
78 ) -> Self {
79 self.attribute.set(ty, cfg);
80 self
81 }
82
83 pub fn with_connectivity(mut self, cfg: ConnectivityConfig) -> Self {
85 self.connectivity = cfg;
86 self
87 }
88
89 pub fn with_edgebreaker(mut self, cfg: EdgebreakerConfig) -> Self {
91 self.connectivity = ConnectivityConfig::Edgebreaker(cfg);
92 self
93 }
94
95 pub fn with_sequential(mut self, cfg: SequentialConfig) -> Self {
97 self.connectivity = ConnectivityConfig::Sequential(cfg);
98 self
99 }
100
101 pub fn with_metadata(mut self, metadata: bool) -> Self {
103 self.metadata = metadata;
104 self
105 }
106
107 pub fn attribute_config(
111 &self,
112 ty: draco_oxide_core::attribute::AttributeType,
113 ) -> AttributeConfig {
114 self.attribute.get(ty)
115 }
116
117 pub fn connectivity(&self) -> &ConnectivityConfig {
119 &self.connectivity
120 }
121
122 pub fn validate(&self) -> Result<(), ConfigError> {
128 use draco_oxide_core::attribute::AttributeType;
129 use draco_oxide_core::codec::connectivity::edgebreaker::EdgebreakerKind;
130
131 if let ConnectivityConfig::Edgebreaker(eb) = &self.connectivity {
133 if eb.traversal == EdgebreakerKind::Predictive {
134 return Err(ConfigError::UnsupportedTraversal);
135 }
136 }
137
138 for (&ty, over) in self.attribute.overrides() {
139 if over.normal_encoding.is_some() && ty != AttributeType::Normal {
140 return Err(ConfigError::NormalEncodingOnNonNormal(ty));
141 }
142
143 if let Some(scheme) = &over.prediction {
144 if !allowed_schemes(ty).iter().any(|s| s == scheme) {
145 return Err(ConfigError::PredictionSchemeForType {
146 ty,
147 scheme: scheme.to_string(),
148 });
149 }
150 }
151
152 if let Some(transform) = over.transform {
153 if !allowed_transforms(ty).contains(&transform) {
154 return Err(ConfigError::TransformForType {
155 ty,
156 transform: format!("{transform:?}"),
157 });
158 }
159 }
160
161 if let Some(quant) = over.quantization {
162 if ty == AttributeType::Normal && !matches!(quant, Quantization::Bits(_)) {
165 return Err(ConfigError::NonBitsQuantizationForNormal);
166 }
167 if let Quantization::Bits(n) = quant {
168 if !(1..=30).contains(&n) {
169 return Err(ConfigError::QuantizationBitsOutOfRange(n));
170 }
171 }
172 }
173 }
174
175 Ok(())
176 }
177}
178
179fn allowed_schemes(
181 ty: draco_oxide_core::attribute::AttributeType,
182) -> Vec<draco_oxide_core::codec::attribute::prediction_scheme::PredictionSchemeType> {
183 use draco_oxide_core::attribute::AttributeType::*;
184 use draco_oxide_core::codec::attribute::prediction_scheme::PredictionSchemeType as S;
185 match ty {
186 Position => vec![
187 S::MeshParallelogramPrediction,
188 S::MeshMultiParallelogramPrediction,
189 S::DeltaPrediction,
190 S::NoPrediction,
191 ],
192 Normal => vec![S::MeshNormalPrediction],
193 TextureCoordinate => vec![
194 S::MeshPredictionForTextureCoordinates,
195 S::DerivativePrediction,
196 S::DeltaPrediction,
197 S::NoPrediction,
198 ],
199 _ => vec![S::DeltaPrediction, S::NoPrediction],
202 }
203}
204
205fn allowed_transforms(
207 ty: draco_oxide_core::attribute::AttributeType,
208) -> Vec<attribute::PredictionTransformType> {
209 use attribute::PredictionTransformType as T;
210 use draco_oxide_core::attribute::AttributeType::*;
211 match ty {
212 Normal => vec![T::OctahedralOrthogonal, T::OctahedralReflection],
214 _ => vec![T::Difference, T::WrappedDifference, T::NoTransform],
215 }
216}
217
218#[remain::sorted]
220#[derive(Error, Debug)]
221pub enum ConfigError {
222 #[error("normals accept only an explicit bit count (octahedral error is angular)")]
223 NonBitsQuantizationForNormal,
224 #[error("normal encoding was set on a non-normal attribute ({0:?})")]
225 NormalEncodingOnNonNormal(draco_oxide_core::attribute::AttributeType),
226 #[error("prediction scheme {scheme} is not valid for attribute type {ty:?}")]
227 PredictionSchemeForType {
228 ty: draco_oxide_core::attribute::AttributeType,
229 scheme: String,
230 },
231 #[error("quantization bits {0} out of range (must be 1..=30)")]
232 QuantizationBitsOutOfRange(u8),
233 #[error("prediction transform {transform} is not valid for attribute type {ty:?}")]
234 TransformForType {
235 ty: draco_oxide_core::attribute::AttributeType,
236 transform: String,
237 },
238 #[error("the selected edgebreaker traversal is not implemented")]
239 UnsupportedTraversal,
240}
241
242#[remain::sorted]
243#[derive(Error, Debug)]
244pub enum Err {
245 #[error("Attribute encoding error: {0}")]
246 AttributeError(#[from] attribute::Err),
247 #[error("Invalid encoder configuration: {0}")]
248 ConfigError(#[from] ConfigError),
249 #[error("Connectivity encoding error: {0}")]
250 ConnectivityError(#[from] connectivity::Err),
251 #[error("Header encoding error: {0}")]
252 HeaderError(#[from] header::Err),
253 #[error("Metadata encoding error: {0}")]
254 MetadataError(#[from] metadata::Err),
255}
256
257pub fn encode<W>(mesh: Mesh, writer: &mut W, cfg: Config) -> Result<(), Err>
259where
260 W: ByteWriter,
261{
262 cfg.validate()?;
264
265 #[cfg(feature = "evaluation")]
266 eval::scope_begin("compression info", writer);
267
268 header::encode_header(writer, &cfg)?;
270
271 debug_write!("Header done, now starting metadata.", writer);
272
273 if cfg.metadata {
275 #[cfg(feature = "evaluation")]
276 eval::scope_begin("metadata", writer);
277 metadata::encode_metadata(&mesh, writer)?;
278 #[cfg(feature = "evaluation")]
279 eval::scope_end(writer);
280 }
281
282 debug_write!("Metadata done, now starting connectivity.", writer);
283
284 let Mesh {
286 mut attributes,
287 faces,
288 ..
289 } = mesh;
290
291 if !attributes
292 .iter()
293 .any(|att| att.get_attribute_type() == draco_oxide_core::attribute::AttributeType::Position)
294 {
295 return Err(Err::ConnectivityError(
296 connectivity::Err::PositionAttributeTypeError,
297 ));
298 }
299
300 let (ds, pos_corner_table) = ds::build_global_ds(faces, &mut attributes);
301 let mut adss = ds::build_attribute_ds(&ds, &pos_corner_table, attributes);
302
303 let corners_of_edgebreaker = connectivity::encode_connectivity(&mut adss, writer, &cfg)?;
305 debug_write!("Connectivity done, now starting attributes.", writer);
306
307 attribute::encode_attributes(adss, corners_of_edgebreaker, writer, &cfg)?;
309
310 debug_write!("All done", writer);
311
312 #[cfg(feature = "evaluation")]
313 eval::scope_end(writer);
314 Ok(())
315}
316
317#[cfg(test)]
318mod config_tests {
319 use super::*;
320 use draco_oxide_core::attribute::AttributeType;
321 use draco_oxide_core::codec::attribute::prediction_scheme::PredictionSchemeType;
322 use draco_oxide_core::codec::connectivity::edgebreaker::EdgebreakerKind;
323
324 #[test]
325 fn default_config_is_valid() {
326 assert!(<Config as ConfigType>::default().validate().is_ok());
327 }
328
329 #[test]
330 fn position_quantization_override_validates() {
331 let cfg = Config::default().with_attribute(
332 AttributeType::Position,
333 AttributeConfig {
334 quantization: Some(Quantization::Bits(14)),
335 ..Default::default()
336 },
337 );
338 assert!(cfg.validate().is_ok());
339 }
340
341 #[test]
342 fn texture_predictor_on_normal_is_rejected() {
343 let cfg = Config::default().with_attribute(
344 AttributeType::Normal,
345 AttributeConfig {
346 prediction: Some(PredictionSchemeType::MeshPredictionForTextureCoordinates),
347 ..Default::default()
348 },
349 );
350 assert!(matches!(
351 cfg.validate(),
352 Err(ConfigError::PredictionSchemeForType { .. })
353 ));
354 }
355
356 #[test]
357 fn max_error_quantization_on_normal_is_rejected() {
358 let cfg = Config::default().with_attribute(
359 AttributeType::Normal,
360 AttributeConfig {
361 quantization: Some(Quantization::MaxError(0.01)),
362 ..Default::default()
363 },
364 );
365 assert!(matches!(
366 cfg.validate(),
367 Err(ConfigError::NonBitsQuantizationForNormal)
368 ));
369 }
370
371 #[test]
372 fn normal_encoding_on_position_is_rejected() {
373 let cfg = Config::default().with_attribute(
374 AttributeType::Position,
375 AttributeConfig {
376 normal_encoding: Some(NormalEncoding::PredictedOnly),
377 ..Default::default()
378 },
379 );
380 assert!(matches!(
381 cfg.validate(),
382 Err(ConfigError::NormalEncodingOnNonNormal(
383 AttributeType::Position
384 ))
385 ));
386 }
387
388 #[test]
389 fn out_of_range_bits_is_rejected() {
390 let cfg = Config::default().with_attribute(
391 AttributeType::Position,
392 AttributeConfig {
393 quantization: Some(Quantization::Bits(40)),
394 ..Default::default()
395 },
396 );
397 assert!(matches!(
398 cfg.validate(),
399 Err(ConfigError::QuantizationBitsOutOfRange(40))
400 ));
401 }
402
403 #[test]
404 fn predictive_edgebreaker_is_rejected() {
405 let cfg = Config::default().with_edgebreaker(EdgebreakerConfig {
406 traversal: EdgebreakerKind::Predictive,
407 use_single_connectivity: false,
408 });
409 assert!(matches!(
410 cfg.validate(),
411 Err(ConfigError::UnsupportedTraversal)
412 ));
413 }
414
415 #[test]
416 fn sequential_selects_sequential_encoder_method() {
417 use draco_oxide_core::codec::header::EncoderMethod;
418 let cfg = Config::default().with_sequential(SequentialConfig::default());
419 assert_eq!(cfg.connectivity.encoder_method(), EncoderMethod::Sequential);
420 }
421}