├── .gitignore ├── CLAUDE.md ├── Cargo.toml ├── LICENSE_APACHE ├── LICENSE_MIT ├── README.md ├── analyzer ├── Cargo.toml ├── README.md ├── assets │ ├── template.html │ └── viewer.js └── src │ └── main.rs ├── cli ├── Cargo.toml └── src │ └── main.rs ├── clippy.toml ├── deny.toml ├── draco-oxide ├── Cargo.toml ├── build.rs ├── examples │ ├── gltf.rs │ └── obj.rs ├── macros │ └── draco-nd-vector │ │ ├── Cargo.toml │ │ └── src │ │ └── lib.rs ├── src │ ├── core │ │ ├── attribute │ │ │ ├── mod.rs │ │ │ └── raw.rs │ │ ├── bit_coder.rs │ │ ├── buffer │ │ │ ├── attribute.rs │ │ │ ├── mod.rs │ │ │ ├── reader.rs │ │ │ └── writer.rs │ │ ├── corner_table │ │ │ ├── all_inclusive_corner_table.rs │ │ │ ├── attribute_corner_table.rs │ │ │ └── mod.rs │ │ ├── material.rs │ │ ├── mesh │ │ │ ├── builder.rs │ │ │ ├── meh_features.rs │ │ │ ├── metadata.rs │ │ │ └── mod.rs │ │ ├── mod.rs │ │ ├── point_cloud │ │ │ └── mod.rs │ │ ├── point_cloud_builder │ │ │ └── mod.rs │ │ ├── scene │ │ │ └── mod.rs │ │ ├── shared.rs │ │ ├── structural_metadata.rs │ │ └── texture.rs │ ├── decode │ │ ├── attribute │ │ │ ├── attribute_decoder.rs │ │ │ ├── inverse_prediction_transform │ │ │ │ ├── difference.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── oct_difference.rs │ │ │ │ ├── oct_orthogonal.rs │ │ │ │ ├── oct_reflection.rs │ │ │ │ └── orthogonal.rs │ │ │ ├── mod.rs │ │ │ └── portabilization │ │ │ │ ├── dequantization_rect_array.rs │ │ │ │ ├── mod.rs │ │ │ │ └── to_bits.rs │ │ ├── connectivity │ │ │ ├── mod.rs │ │ │ ├── sequential.rs │ │ │ └── spirale_reversi.rs │ │ ├── entropy │ │ │ ├── mod.rs │ │ │ ├── rans.rs │ │ │ └── symbol_coding.rs │ │ ├── header │ │ │ └── mod.rs │ │ ├── metadata │ │ │ └── mod.rs │ │ └── mod.rs │ ├── encode │ │ ├── attribute │ │ │ ├── attribute_encoder.rs │ │ │ ├── mod.rs │ │ │ ├── portabilization │ │ │ │ ├── mod.rs │ │ │ │ ├── octahedral_quantization.rs │ │ │ │ ├── quantization_coordinate_wise.rs │ │ │ │ └── to_bits.rs │ │ │ └── prediction_transform │ │ │ │ ├── difference.rs │ │ │ │ ├── geom.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── oct_orthogonal.rs │ │ │ │ ├── oct_reflection.rs │ │ │ │ ├── orthogonal.rs │ │ │ │ └── wrapped_difference.rs │ │ ├── connectivity │ │ │ ├── config.rs │ │ │ ├── edgebreaker.rs │ │ │ ├── mod.rs │ │ │ └── sequential.rs │ │ ├── entropy │ │ │ ├── mod.rs │ │ │ ├── rans.rs │ │ │ └── symbol_coding.rs │ │ ├── header │ │ │ └── mod.rs │ │ ├── metadata │ │ │ └── mod.rs │ │ └── mod.rs │ ├── eval.rs │ ├── io │ │ ├── gltf │ │ │ ├── decode.rs │ │ │ ├── encode.rs │ │ │ ├── mod.rs │ │ │ ├── scene_io.rs │ │ │ └── transcoder.rs │ │ ├── mod.rs │ │ ├── obj │ │ │ └── mod.rs │ │ └── texture_io.rs │ ├── lib.rs │ ├── shared │ │ ├── attribute │ │ │ ├── mod.rs │ │ │ ├── portabilization │ │ │ │ ├── mod.rs │ │ │ │ └── spherical.rs │ │ │ ├── prediction_scheme │ │ │ │ ├── delta_prediction.rs │ │ │ │ ├── derivative_prediction.rs │ │ │ │ ├── mesh_multi_parallelogram_prediction.rs │ │ │ │ ├── mesh_normal_prediction.rs │ │ │ │ ├── mesh_parallelogram_prediction.rs │ │ │ │ ├── mesh_prediction_for_texture_coordinates.rs │ │ │ │ └── mod.rs │ │ │ ├── prediction_transform │ │ │ │ ├── difference.rs │ │ │ │ ├── geom.rs │ │ │ │ ├── mod.rs │ │ │ │ ├── oct_difference.rs │ │ │ │ ├── oct_orthogonal.rs │ │ │ │ ├── oct_reflection.rs │ │ │ │ └── orthogonal.rs │ │ │ └── sequence.rs │ │ ├── connectivity │ │ │ ├── edgebreaker │ │ │ │ ├── mod.rs │ │ │ │ ├── prediction.rs │ │ │ │ └── symbol_encoder.rs │ │ │ ├── eq.rs │ │ │ ├── mod.rs │ │ │ └── sequential.rs │ │ ├── entropy │ │ │ └── mod.rs │ │ └── mod.rs │ └── utils │ │ ├── bit_coder.rs │ │ ├── debug.rs │ │ ├── geom.rs │ │ └── mod.rs └── tests │ ├── compatibility.rs │ ├── data │ ├── Duck │ │ ├── Duck.glb │ │ ├── Duck.gltf │ │ ├── Duck0.bin │ │ ├── DuckCM.png │ │ └── README.md │ ├── Triangle.gltf │ ├── bunny.obj │ ├── cube_quads.obj │ ├── punctured_sphere.obj │ ├── simpleTriangle.bin │ ├── sphere.obj │ ├── tetrahedron.obj │ └── torus.obj │ ├── eval │ └── mod.rs │ └── integrated_tests.rs ├── rust-toolchain.toml ├── rustfmt.toml └── util ├── analyze_gltf_files.py ├── extract_draco_binary.py └── extract_glb_json.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | debug/ 4 | target/ 5 | 6 | # These are backup files generated by rustfmt 7 | **/*.rs.bk 8 | 9 | # MSVC Windows builds of rustc generate these, which store debugging information 10 | *.pdb 11 | Cargo.lock 12 | 13 | # analyzer outputs 14 | analyzer/outputs 15 | 16 | # test outputs 17 | draco-oxide/tests/outputs 18 | 19 | # private test data 20 | private_tests -------------------------------------------------------------------------------- /CLAUDE.md: -------------------------------------------------------------------------------- 1 | # CLAUDE.md 2 | 3 | This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. 4 | 5 | ## Project Overview 6 | 7 | draco-rs is a Rust implementation of Google's Draco mesh compression library for compressing and decompressing 3D geometric meshes and point clouds. This is currently a Work In Progress (WIP) - the encoder is functional while the decoder is largely incomplete. 8 | 9 | ## Workspace Structure 10 | 11 | This is a Cargo workspace with three crates: 12 | - **draco-rs/** - Main compression library (published as "draco" crate) 13 | - **cli/** - Command-line interface (minimal implementation) 14 | - **analyzer/** - Mesh analysis tool with HTML visualization reports 15 | 16 | ## Common Commands 17 | 18 | ### Building 19 | ```bash 20 | # Build entire workspace 21 | cargo build 22 | 23 | # Build with evaluation features (required for analysis) 24 | cargo build --features evaluation 25 | 26 | # Build specific crate 27 | cargo build -p draco 28 | cargo build -p analyzer 29 | ``` 30 | 31 | ### Testing 32 | ```bash 33 | # Run all tests 34 | cargo test 35 | 36 | # Run tests with evaluation features 37 | cargo test --features evaluation 38 | 39 | # Run specific test suites 40 | cargo test compatibility # Basic encoding tests 41 | cargo test integrated_tests # Full encode/decode cycles 42 | cargo test obj_reindexing # OBJ file processing 43 | cargo test eval # Evaluation tests (requires --features evaluation) 44 | ``` 45 | 46 | ### Code Quality 47 | ```bash 48 | cargo fmt # Format code 49 | cargo clippy # Run lints (configured with MSRV 1.84) 50 | cargo deny check # Check licenses and dependencies 51 | ``` 52 | 53 | ## Architecture Overview 54 | 55 | ### Core Data Structures 56 | - **Mesh**: Central data structure in `core/mesh/` containing faces and attributes 57 | - **Attributes**: Vertex data (position, normal, texture coords) managed in `core/attribute/` 58 | - **Corner Table**: Topological representation for mesh connectivity in `core/corner_table/` 59 | 60 | ### Compression Pipeline 61 | 1. **Connectivity Compression**: Uses Edgebreaker algorithm (`encode/connectivity/edgebreaker.rs`) 62 | 2. **Attribute Compression**: Prediction transforms and quantization (`encode/attribute/`) 63 | 3. **Entropy Coding**: rANS (range Asymmetric Numeral Systems) in `encode/entropy/` 64 | 65 | ### Key Modules 66 | - **encode/**: Complete encoding pipeline (functional) 67 | - **decode/**: Decoding pipeline (mostly incomplete/commented out) 68 | - **shared/**: Common algorithms and data structures 69 | - **io/**: File format support (OBJ, STL, partial glTF) 70 | - **utils/**: Bit manipulation and geometric utilities 71 | 72 | ## Features and Configuration 73 | 74 | ### Cargo Features 75 | - `evaluation`: Enables compression analysis and metrics generation 76 | - `debug_format`: Additional debug output formatting 77 | 78 | ### Test Data 79 | Test meshes are located in `draco-rs/tests/data/`: 80 | - bunny.obj, sphere.obj, tetrahedron.obj, triangle.obj, torus.obj 81 | 82 | ### Analysis and Evaluation 83 | When using `--features evaluation`, you can: 84 | - Generate detailed compression metrics 85 | - Compare L2 norm distances between original and compressed meshes 86 | - Create HTML visualization reports via the analyzer tool 87 | 88 | ## Development Notes 89 | 90 | ### Current Limitations 91 | - Decoder implementation is incomplete (most functionality commented out) 92 | - CLI tool has minimal functionality 93 | - File format support limited to OBJ/STL with partial glTF 94 | 95 | ### Testing Patterns 96 | Tests typically follow this pattern: 97 | 1. Load test mesh from `tests/data/` using `tobj` 98 | 2. Convert to internal `Mesh` structure using `MeshBuilder` 99 | 3. Encode using `encode()` function with configuration 100 | 4. For evaluation tests, use `EvalWriter` to capture metrics 101 | 102 | ### Dependencies 103 | - `tobj`: OBJ file parsing 104 | - `faer`: Linear algebra operations 105 | - `serde`: Serialization for configuration and evaluation data 106 | - `nd_vector`: Custom macro for N-dimensional vectors 107 | 108 | ## Version Requirements 109 | - Rust 1.84+ (specified in rust-toolchain.toml) 110 | - Edition 2021 -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["draco-oxide", "cli", "analyzer"] 4 | -------------------------------------------------------------------------------- /LICENSE_MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2025 Eukarya, Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # draco-oxide 2 | 3 | 4 | 5 | [![Crates.io](https://img.shields.io/crates/v/draco-oxide)](https://crates.io/crates/draco-oxide) 6 | [![Documentation](https://docs.rs/draco-oxide/badge.svg)](https://docs.rs/draco-oxide) 7 | 8 | `draco-oxide` is a high-performance Rust re-write of Google’s [Draco](https://github.com/google/draco) 3D-mesh compression library, featuring efficient streaming I/O and seamless WebAssembly integration. 9 | 10 | > **Status:** **Alpha** – Encoder is functional; decoder implementation is **work‑in‑progress**. 11 | 12 | --- 13 | 14 | ## Features 15 | 16 | | Component | Alpha | Beta Roadmap | 17 | | ------------------ | ----- | ------------------ | 18 | | Mesh Encoder | ✅ | Performance optimization | 19 | | Mesh Decoder | ❌ | ✅ | 20 | | glTF Transcoder (basic)| ✅ | Animation and many more extensions | 21 | 22 | ### Encoder Highlights 23 | 24 | * Triangle‑mesh compression with configurable speed/ratio presets. 25 | * Basic glTF transcoder (`*.gltf` or `*.glb` → `*.glb` with mesh buffer compressed via [KHR_draco_mesh_compression extension](https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_draco_mesh_compression)). 26 | * Pure‑Rust implementation. 27 | * `no_std` + `alloc` compatible; builds to **WASM32**, **x86\_64**, **aarch64**, and more. 28 | 29 | ### Decoder (Coming Soon) 30 | 31 | Planned for the **beta** milestone. 32 | 33 | --- 34 | 35 | ## Getting Started 36 | 37 | ### Add to Your Project 38 | 39 | ```txt 40 | draco-oxide = "0.1.0-alpha.1" 41 | ``` 42 | 43 | ### Example: Encode an obj file. 44 | 45 | ```rust 46 | use draco_oxide::{encode::{self, encode}, io::obj::load_obj}; 47 | use draco_oxide::prelude::ConfigType; 48 | use std::io::Write; 49 | 50 | fn main() -> Result<(), Box> { 51 | // Create mesh from an obj file. 52 | let mesh = load_obj("mesh.obj").unwrap(); 53 | 54 | // Create a buffer that we write the encoded data to. 55 | // This time we use 'Vec' as the output buffer, but 56 | // draco-oxide can stream-write to anything 57 | // that implements 'draco_oxide::prelude::ByteWriter'. 58 | let mut buffer = Vec::new(); 59 | 60 | // Encode the mesh into the buffer. 61 | encode(mesh, &mut buffer, encode::Config::default()).unwrap(); 62 | 63 | let mut file = std::fs::File::create("output.drc").unwrap(); 64 | file.write_all(&buffer)?; 65 | Ok(()) 66 | } 67 | ``` 68 | 69 | See the [draco-oxide/examples](draco-oxide/examples/) directory for more. 70 | 71 | ### CLI 72 | 73 | ```bash 74 | # compress input.obj into a draco file output.drc 75 | cargo run --bin cli -- -i path/to/input.obj -o path/to/output.drc 76 | 77 | # transcode input.glb into a draco compressed glb file output.glb as specified 78 | # in KHR_draco_mesh_compression extension. 79 | cargo run --bin cli -- --transcode -i path/to/input.glb -o path/to/output.glb 80 | ``` 81 | --- 82 | 83 | ## Roadmap to Beta 84 | 85 | * Decoder Support. 86 | * Complete glTF support. 87 | 88 | --- 89 | 90 | ## Acknowledgements 91 | 92 | * **Google Draco** – original C++ implementation 93 | 94 | --- 95 | 96 | ## Contact 97 | 98 | Re:Earth core committers: [community@reearth.io](mailto:community@reearth.io) 99 | 100 | --- 101 | 102 | ## License 103 | 104 | Licensed under either (at your discretion): 105 | 106 | - Apache License, Version 2.0 107 | ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 108 | - MIT license 109 | ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 110 | -------------------------------------------------------------------------------- /analyzer/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "analyzer" 3 | version = "0.1.0" 4 | edition = "2024" 5 | 6 | [dependencies] 7 | clap = { version = "4", features = ["derive"] } 8 | chrono = "*" 9 | draco-oxide = { path = "../draco-oxide", features = ["evaluation"]} 10 | serde = { version = "1.0.219", features = ["derive"]} 11 | serde_json = "1.0" 12 | tobj = "4.0.3" 13 | regex = "1.11.1" -------------------------------------------------------------------------------- /analyzer/README.md: -------------------------------------------------------------------------------- 1 | # Draco Mesh Analyzer 2 | 3 | A tool for analyzing Draco compression performance and generating HTML reports. 4 | 5 | ## Usage 6 | 7 | ```bash 8 | cargo run --bin analyzer -- --original path/to/mesh.obj 9 | cargo run --bin analyzer -- --original path/to/model.glb 10 | ``` 11 | 12 | ## Output 13 | 14 | Creates timestamped output directory in `analyzer/outputs/` with: 15 | - **index.html**: Interactive analysis report 16 | - **Compressed files**: Draco-compressed versions 17 | - **Evaluation data**: JSON metrics 18 | - **Comparison files**: Decompressed versions for quality analysis 19 | 20 | ## Supported Formats 21 | 22 | - **Input**: OBJ, glTF, GLB 23 | - **Output**: DRC, HTML reports with compression metrics 24 | 25 | ## Building 26 | 27 | ```bash 28 | cargo build --bin analyzer --features evaluation 29 | ``` 30 | 31 | ## Features 32 | 33 | - Compression ratio analysis 34 | - Quality metrics (L2 norm distance) 35 | - File size comparisons 36 | - Interactive HTML reports with detailed breakdowns -------------------------------------------------------------------------------- /cli/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "cli" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | draco-oxide = { path = "../draco-oxide" } 8 | clap = { version = "4.0", features = ["derive"] } 9 | anyhow = "1.0" 10 | -------------------------------------------------------------------------------- /cli/src/main.rs: -------------------------------------------------------------------------------- 1 | use clap::Parser; 2 | use anyhow::Result; 3 | use std::path::Path; 4 | use draco_oxide::prelude::ConfigType; 5 | 6 | #[derive(Parser)] 7 | #[command(name = "draco-cli")] 8 | #[command(about = "A CLI tool for Draco mesh compression")] 9 | struct Cli { 10 | /// Input file path 11 | #[arg(short, long)] 12 | input: String, 13 | 14 | /// Output file path 15 | #[arg(short, long)] 16 | output: String, 17 | 18 | /// Transcode mode for glTF/GLB files (compress with Draco) 19 | #[arg(long)] 20 | transcode: bool, 21 | } 22 | 23 | fn main() -> Result<()> { 24 | let cli = Cli::parse(); 25 | 26 | if cli.transcode { 27 | transcode_gltf(&cli.input, &cli.output) 28 | } else { 29 | convert_obj_to_drc(&cli.input, &cli.output) 30 | } 31 | } 32 | 33 | fn convert_obj_to_drc(input_path: &str, output_path: &str) -> Result<()> { 34 | 35 | // Check input file extension 36 | let input_ext = Path::new(input_path) 37 | .extension() 38 | .and_then(|s| s.to_str()) 39 | .unwrap_or(""); 40 | 41 | if input_ext != "obj" { 42 | anyhow::bail!("Input file must be a .obj file for conversion mode"); 43 | } 44 | 45 | // Check output file extension 46 | let output_ext = Path::new(output_path) 47 | .extension() 48 | .and_then(|s| s.to_str()) 49 | .unwrap_or(""); 50 | 51 | if output_ext != "drc" { 52 | anyhow::bail!("Output file must be a .drc file for conversion mode"); 53 | } 54 | 55 | // Load OBJ file using draco-oxide's OBJ loader 56 | let mesh = draco_oxide::io::obj::load_obj(input_path) 57 | .map_err(|e| anyhow::anyhow!("Failed to load OBJ file: {:?}", e))?; 58 | 59 | 60 | // Configure compression settings 61 | let config = draco_oxide::encode::Config::default(); 62 | 63 | // Encode the mesh to a buffer 64 | let mut buffer = Vec::new(); 65 | draco_oxide::encode::encode(mesh, &mut buffer, config) 66 | .map_err(|e| anyhow::anyhow!("Failed to encode mesh: {:?}", e))?; 67 | 68 | // Write to output file 69 | std::fs::write(output_path, buffer) 70 | .map_err(|e| anyhow::anyhow!("Failed to write output file: {}", e))?; 71 | 72 | Ok(()) 73 | } 74 | 75 | fn transcode_gltf(input_path: &str, output_path: &str) -> Result<()> { 76 | 77 | // Check input file extension 78 | let input_ext = Path::new(input_path) 79 | .extension() 80 | .and_then(|s| s.to_str()) 81 | .unwrap_or(""); 82 | 83 | if !matches!(input_ext, "gltf" | "glb") { 84 | anyhow::bail!("Input file must be a .gltf or .glb file for transcode mode"); 85 | } 86 | 87 | // Check output file extension 88 | let output_ext = Path::new(output_path) 89 | .extension() 90 | .and_then(|s| s.to_str()) 91 | .unwrap_or(""); 92 | 93 | if !matches!(output_ext, "gltf" | "glb") { 94 | anyhow::bail!("Output file must be a .gltf or .glb file for transcode mode"); 95 | } 96 | 97 | // Create transcoder with default options 98 | let mut transcoder = draco_oxide::io::gltf::transcoder::DracoTranscoder::create(None) 99 | .map_err(|e| anyhow::anyhow!("Failed to create transcoder: {:?}", e))?; 100 | 101 | // Set up file options 102 | let file_options = draco_oxide::io::gltf::transcoder::FileOptions::new( 103 | input_path.to_string(), 104 | output_path.to_string(), 105 | ); 106 | 107 | // Perform transcoding 108 | transcoder.transcode_file(&file_options) 109 | .map_err(|e| anyhow::anyhow!("Failed to transcode: {:?}", e))?; 110 | 111 | Ok(()) 112 | } -------------------------------------------------------------------------------- /clippy.toml: -------------------------------------------------------------------------------- 1 | msrv = "1.84" 2 | -------------------------------------------------------------------------------- /deny.toml: -------------------------------------------------------------------------------- 1 | # Temporarily exclude rusoto and ethers-providers from bans since we've yet to transition to the 2 | exclude = ["rusoto_core", "rusoto_kms", "rusoto_credential", "ethers-providers", "tungstenite"] 3 | 4 | # This section is considered when running `cargo deny check advisories` 5 | # More documentation for the advisories section can be found here: 6 | # https://embarkstudios.github.io/cargo-deny/checks/advisories/cfg.html 7 | [advisories] 8 | vulnerability = "warn" 9 | unmaintained = "warn" 10 | yanked = "warn" 11 | notice = "warn" 12 | ignore = [ 13 | "RUSTSEC-2020-0071", # https://rustsec.org/advisories/RUSTSEC-2020-0071 14 | ] 15 | 16 | # This section is considered when running `cargo deny check bans`. 17 | # More documentation about the 'bans' section can be found here: 18 | # https://embarkstudios.github.io/cargo-deny/checks/bans/cfg.html 19 | [bans] 20 | # Lint level for when multiple versions of the same crate are detected 21 | multiple-versions = "warn" 22 | # Lint level for when a crate version requirement is `*` 23 | wildcards = "allow" 24 | highlight = "all" 25 | # List of crates to deny 26 | deny = [] 27 | # Certain crates/versions that will be skipped when doing duplicate detection. 28 | skip = [] 29 | # Similarly to `skip` allows you to skip certain crates during duplicate 30 | # detection. Unlike skip, it also includes the entire tree of transitive 31 | # dependencies starting at the specified crate, up to a certain depth, which is 32 | # by default infinite 33 | skip-tree = [] 34 | 35 | [licenses] 36 | unlicensed = "warn" 37 | # List of explicitly allowed licenses 38 | # See https://spdx.org/licenses/ for list of possible licenses 39 | # [possible values: any SPDX 3.7 short identifier (+ optional exception)]. 40 | allow = [ 41 | "MIT", 42 | "Apache-2.0", 43 | "Apache-2.0 WITH LLVM-exception", 44 | "BSD-2-Clause", 45 | "BSD-3-Clause", 46 | "ISC", 47 | "Unicode-DFS-2016", 48 | "OpenSSL", 49 | "Unlicense", 50 | "WTFPL", 51 | "BSL-1.0", 52 | "0BSD", 53 | "MPL-2.0", 54 | ] 55 | 56 | # Allow 1 or more licenses on a per-crate basis, so that particular licenses 57 | # aren't accepted for every possible crate as with the normal allow list 58 | exceptions = [ 59 | # CC0 is a permissive license but somewhat unclear status for source code 60 | # so we prefer to not have dependencies using it 61 | # https://tldrlegal.com/license/creative-commons-cc0-1.0-universal 62 | { allow = ["CC0-1.0"], name = "secp256k1" }, 63 | { allow = ["CC0-1.0"], name = "secp256k1-sys" }, 64 | { allow = ["CC0-1.0"], name = "tiny-keccak" }, 65 | { allow = ["CC0-1.0"], name = "to_method" }, 66 | { allow = ["CC0-1.0"], name = "more-asserts" }, 67 | { allow = ["CC0-1.0"], name = "trezor-client" }, 68 | { allow = ["CC0-1.0"], name = "notify" }, 69 | { allow = ["CC0-1.0"], name = "constant_time_eq" }, 70 | { allow = ["CC0-1.0"], name = "dunce" }, 71 | { allow = ["CC0-1.0"], name = "aurora-engine-modexp" }, 72 | 73 | { allow = ["GPL-3.0"], name = "fastrlp" }, 74 | { allow = ["GPL-3.0"], name = "fastrlp-derive" }, 75 | ] 76 | #copyleft = "deny" 77 | 78 | # See note in unicode-ident's readme! 79 | [[licenses.clarify]] 80 | name = "unicode-ident" 81 | version = "*" 82 | expression = "(MIT OR Apache-2.0) AND Unicode-DFS-2016" 83 | license-files = [ 84 | { path = "LICENSE-UNICODE", hash = 0x3fb01745 } 85 | ] 86 | [[licenses.clarify]] 87 | name = "ring" 88 | version = "*" 89 | expression = "OpenSSL" 90 | license-files = [ 91 | { path = "LICENSE", hash = 0xbd0eed23 } 92 | ] 93 | 94 | # This section is considered when running `cargo deny check sources`. 95 | # More documentation about the 'sources' section can be found here: 96 | # https://embarkstudios.github.io/cargo-deny/checks/sources/cfg.html 97 | [sources] 98 | # Lint level for what to happen when a crate from a crate registry that is not 99 | # in the allow list is encountered 100 | unknown-registry = "warn" 101 | # Lint level for what to happen when a crate from a git repository that is not 102 | # in the allow list is encountered 103 | unknown-git = "allow" 104 | -------------------------------------------------------------------------------- /draco-oxide/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | description = "draco-oxide is a rust rewrite of Google's draco mesh compression library." 3 | name = "draco-oxide" 4 | authors = ["Re:Earth Contributors"] 5 | edition = "2021" 6 | exclude = ["benches/", "test-data/", "testdata/"] 7 | rust-version = "1.84" 8 | version = "0.1.0-alpha.2" 9 | license = "MIT OR Apache-2.0" 10 | homepage = "https://github.com/reearth/draco-oxide" 11 | repository = "https://github.com/reearth/draco-oxide" 12 | readme = "../README.md" 13 | 14 | [dependencies] 15 | enum_dispatch = "0.3.13" 16 | draco-nd-vector = { path = "macros/draco-nd-vector", version = "0.0.0" } 17 | lazy_static = "1.5.0" 18 | remain = "0.2.15" 19 | seq-macro = "0.3.6" 20 | serde = { version = "1.0", features = ["derive"] } 21 | schemars = "0.8" 22 | serde_json = "1.0" 23 | thiserror = "2.0.12" 24 | tobj = "4.0.3" 25 | faer = "0.22.6" 26 | gltf = { version = "1.4", features = ["extensions", "guess_mime_type", "image", "import"] } 27 | image = { version = "0.25", features = ["webp"] } 28 | base64 = "0.21" 29 | indexmap = "2.0" 30 | paste = "1.0" 31 | 32 | 33 | [features] 34 | default = [] 35 | evaluation = [] 36 | debug_format = [] -------------------------------------------------------------------------------- /draco-oxide/build.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("cargo:rerun-if-env-changed=MAX_VECTOR_DIM"); 3 | } -------------------------------------------------------------------------------- /draco-oxide/examples/gltf.rs: -------------------------------------------------------------------------------- 1 | use draco_oxide::io::gltf::transcoder::DracoTranscoder; 2 | fn main() { 3 | // input file 4 | let input = "input.gltf"; 5 | 6 | // output file 7 | let output = "output.glb"; 8 | 9 | // Create transcoder with default options 10 | let mut transcoder = DracoTranscoder::create(None).unwrap(); 11 | 12 | // Set up file options 13 | let file_options = draco_oxide::io::gltf::transcoder::FileOptions::new( 14 | input.to_string(), 15 | output.to_string(), 16 | ); 17 | 18 | // transcode the GLTF file 19 | transcoder.transcode_file(&file_options).unwrap(); 20 | } -------------------------------------------------------------------------------- /draco-oxide/examples/obj.rs: -------------------------------------------------------------------------------- 1 | use draco_oxide::{encode::{self, encode}, io::obj::load_obj}; 2 | use draco_oxide::prelude::ConfigType; 3 | use std::io::Write; 4 | 5 | fn main() -> Result<(), Box> { 6 | // Create mesh from an obj file, for example. 7 | let mesh = load_obj("mesh.obj").unwrap(); 8 | 9 | // Create a buffer that we write the encoded data to. 10 | // This time we use 'Vec' as the output buffer, but draco-oxide can stream-write to anything 11 | // that implements draco_oxide::prelude::ByteWriter. 12 | let mut buffer = Vec::new(); 13 | 14 | // Encode the mesh into the buffer. 15 | encode(mesh, &mut buffer, encode::Config::default()).unwrap(); 16 | 17 | let mut file = std::fs::File::create("output.drc").unwrap(); 18 | file.write_all(&buffer)?; 19 | Ok(()) 20 | } -------------------------------------------------------------------------------- /draco-oxide/macros/draco-nd-vector/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "draco-nd-vector" 3 | version = "0.0.0" 4 | edition = "2024" 5 | description = "Contains macros that writes low dimensional vectors implementation." 6 | license = "MIT OR Apache-2.0" 7 | 8 | [lib] 9 | proc-macro = true 10 | 11 | [dependencies] 12 | syn = "2.0" 13 | quote = "1.0" 14 | seq-macro = "0.3.6" -------------------------------------------------------------------------------- /draco-oxide/src/core/attribute/raw.rs: -------------------------------------------------------------------------------- 1 | use crate::core::buffer; 2 | use crate::core::shared::{DataValue, Vector}; 3 | use crate::core::attribute::AttributeType; 4 | use crate::core::attribute::ComponentDataType; 5 | 6 | pub struct Attribute { 7 | buffer: buffer::attribute::AttributeBuffer, 8 | 9 | /// id of the parents, if any 10 | parent_ids: Vec, 11 | 12 | /// attribute type 13 | att_type: AttributeType, 14 | } 15 | 16 | impl Attribute { 17 | pub fn from(data: Vec, att_type: AttributeType) -> Self 18 | where Data: Vector, 19 | { 20 | let buffer = buffer::attribute::AttributeBuffer::from(data); 21 | Self { 22 | buffer, 23 | parent_ids: Vec::new(), 24 | att_type 25 | } 26 | } 27 | 28 | pub fn from_faces(data: Vec<[usize; 3]>) -> Self { 29 | let buffer = buffer::attribute::AttributeBuffer::from(data); 30 | Self { 31 | buffer, 32 | parent_ids: Vec::new(), 33 | att_type: AttributeType::Connectivity, 34 | } 35 | } 36 | 37 | pub fn get(&self, idx: usize) -> Data 38 | where 39 | Data: Vector, 40 | Data::Component: DataValue 41 | { 42 | self.buffer.get(idx) 43 | } 44 | 45 | pub fn get_component_type(&self) -> ComponentDataType { 46 | self.buffer.get_component_type() 47 | } 48 | 49 | pub fn get_num_components(&self) -> usize { 50 | self.buffer.get_num_components() 51 | } 52 | 53 | pub fn get_attribute_type(&self) -> AttributeType { 54 | self.att_type 55 | } 56 | 57 | pub fn get_parent_ids(&self) -> &[usize] { 58 | &self.parent_ids 59 | } 60 | 61 | #[inline(always)] 62 | pub fn len(&self) -> usize { 63 | self.buffer.len() 64 | } 65 | #[inline] 66 | /// returns the data values as a slice of values casted to the given type. 67 | /// # Safety: 68 | /// This function assumes that the buffer's data is properly aligned and matches the type `Data`. 69 | pub unsafe fn as_slice_unchecked(&self) -> &[Data] 70 | { 71 | // Safety: upheld 72 | self.buffer.as_slice::() 73 | } 74 | 75 | #[inline] 76 | /// returns the data values as a mutable slice of values casted to the given type. 77 | /// # Safety: 78 | /// This function assumes that the buffer's data is properly aligned and matches the type `Data`. 79 | pub unsafe fn as_slice_unchecked_mut(&mut self) -> &mut [Data] 80 | { 81 | // Safety: upheld 82 | self.buffer.as_slice_mut::() 83 | } 84 | 85 | pub(crate) fn from_with_parent_ids(att: super::Attribute, ids: Vec) -> Self { 86 | Self { 87 | buffer: att.buffer, 88 | parent_ids: ids, 89 | att_type: att.att_type, 90 | } 91 | } 92 | 93 | pub(crate) fn from_parentless(att: super::Attribute) -> Self { 94 | Self { 95 | buffer: att.buffer, 96 | parent_ids: Vec::new(), 97 | att_type: att.att_type, 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /draco-oxide/src/core/buffer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod attribute; 2 | // pub mod reader; 3 | // pub mod writer; 4 | 5 | use std::{ 6 | alloc, fmt, ptr 7 | }; 8 | 9 | 10 | pub trait OrderConfig { 11 | const IS_MSB_FIRST: bool; 12 | } 13 | 14 | #[derive(Debug)] 15 | pub struct MsbFirst; 16 | 17 | #[derive(Debug)] 18 | pub struct LsbFirst; 19 | 20 | impl OrderConfig for MsbFirst { 21 | const IS_MSB_FIRST: bool = true; 22 | } 23 | impl OrderConfig for LsbFirst { 24 | const IS_MSB_FIRST: bool = false; 25 | } 26 | 27 | 28 | pub struct Buffer { 29 | data: RawBuffer, 30 | 31 | /// length of the buffer, i.e. the number of bits stored in the buffer. 32 | /// The minimum number of bytes allocated for the buffer is 'len' / 8 + 1. 33 | len: usize, 34 | 35 | _phantom: std::marker::PhantomData, 36 | } 37 | 38 | #[allow(dead_code)] 39 | impl Buffer { 40 | /// constructs an empty buffer 41 | pub fn new() -> Self { 42 | Self { data: RawBuffer::new(), len: 0, _phantom: std::marker::PhantomData } 43 | } 44 | 45 | /// A constructor that allocates the specified size (in bits) beforehand. 46 | pub fn with_len(len: usize) -> Self { 47 | let cap = (len + 7) >> 3; 48 | let data = RawBuffer::with_capacity(cap); 49 | Self { data, len, _phantom: std::marker::PhantomData } 50 | } 51 | 52 | 53 | /// returns the number of bits stored in the buffer. 54 | pub fn len(&self) -> usize { 55 | self.len 56 | } 57 | 58 | /// returns the data as a slice of u8. 59 | pub fn as_slice(&self) -> &[u8] { 60 | // Safety: The buffer is guaranteed to be initialized with this size. 61 | unsafe { std::slice::from_raw_parts(self.data.as_ptr(), (self.len + 7) >> 3) } 62 | } 63 | } 64 | 65 | 66 | impl fmt::Debug for Buffer { 67 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 68 | for n in 0..(self.len+7) >> 3 { 69 | write!(f, "{:02x} ", unsafe{ *self.data.as_ptr().add(n) })?; 70 | } 71 | write!(f, "len: {}", self.len)?; 72 | Ok(()) 73 | } 74 | } 75 | 76 | 77 | struct RawBuffer { 78 | data: ptr::NonNull, 79 | 80 | /// the size of the allocation in bytes. 81 | /// The number of bits that can be stored in the buffer is 'cap' * 8. 82 | cap: usize, 83 | } 84 | 85 | impl RawBuffer { 86 | fn new() -> Self { 87 | Self { data: ptr::NonNull::dangling(), cap: 0 } 88 | } 89 | 90 | /// constructs a new buffer with the given capacity. 91 | /// 'cap' must be given in bytes. 92 | fn with_capacity(cap: usize) -> Self { 93 | let data = unsafe { alloc::alloc(alloc::Layout::array::(cap).unwrap()) }; 94 | Self { data: ptr::NonNull::new(data).unwrap(), cap } 95 | } 96 | 97 | /// expands the buffer to 'new_cap'. 98 | /// Safety: 'new_cap' must be less than 'usize::Max'. 99 | unsafe fn expand(&mut self, new_cap: usize) { 100 | debug_assert!(new_cap < usize::MAX, "'new_cap' is too large"); 101 | let new_data = 102 | alloc::realloc(self.data.as_ptr() as *mut u8, alloc::Layout::array::(self.cap).unwrap(), new_cap); 103 | self.data = ptr::NonNull::new(new_data).unwrap_or_else(|| { 104 | alloc::handle_alloc_error(alloc::Layout::array::(new_cap).unwrap()) 105 | }); 106 | self.cap = new_cap; 107 | } 108 | 109 | /// doubles the capacity of the buffer. 110 | fn double(&mut self) { 111 | let new_cap = self.cap * 2; 112 | assert!(new_cap < usize::MAX, "'new_cap' is too large"); 113 | // Safety: Just checked that 'new_cap' is less than 'usize::Max'. 114 | unsafe{ self.expand(new_cap); } 115 | } 116 | 117 | fn as_ptr(&self) -> *mut u8 { 118 | self.data.as_ptr() 119 | } 120 | 121 | fn from_vec(v: Vec) -> Self { 122 | let cap = v.len() * std::mem::size_of::(); 123 | let data = v.as_ptr() as *mut u8; 124 | // forget the value to prevent double free 125 | std::mem::forget(v); 126 | Self { data: ptr::NonNull::new(data).unwrap(), cap } 127 | } 128 | } 129 | 130 | impl fmt::Debug for RawBuffer { 131 | fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { 132 | for n in 0..self.cap { 133 | write!(f, "{:02x} ", unsafe{ *self.data.as_ptr().add(n) })?; 134 | } 135 | Ok(()) 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /draco-oxide/src/core/buffer/reader.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{BitReader, ByteReader}; 2 | 3 | use super::{OrderConfig, RawBuffer, MsbFirst}; 4 | use std::{io::BufReader, ptr}; 5 | 6 | /// Reads the data of the buffer by consuming the buffer. 7 | /// Mostly used by the decoder. 8 | #[repr(C)] 9 | pub struct ByteReader { 10 | ptr: *const u8, 11 | 12 | /// the number of bits remaining in the buffer. 13 | num_remaining_bits: usize, 14 | 15 | /// the position in the current byte being read. It is always less than 8. 16 | pos_in_curr_byte: u8, 17 | 18 | /// The buffer. We want him to die at the very end for deallocation. 19 | _buffer: RawBuffer, 20 | } 21 | 22 | 23 | impl BitReader for Reader { 24 | type ByteReader = Reader; 25 | /// read the 'size' bits of data at the current offset. 26 | /// the output data is stored in the first 'size' bits. 27 | fn read_bits(&mut self, size: u8) -> u64 { 28 | assert!(size <= 64 && size > 0, "Invalid size: {}", size); 29 | assert!(size as usize <= self.num_remaining_bits, "Attempt to read beyond buffer bounds."); 30 | unsafe{ self.read_bits_unchecked(size) } 31 | } 32 | 33 | fn into_byte_reader(&mut self) -> &mut Self::ByteReader { 34 | if self.pos_in_curr_byte != 0 { 35 | self.num_remaining_bits = self.num_remaining_bits - (8 - self.pos_in_curr_byte) as usize; 36 | self.ptr = unsafe { self.ptr.add(1) }; 37 | self.pos_in_curr_byte = 0; 38 | } 39 | 40 | // Safety: we forced the memory layout of `Reader` and `Reader` to be the same. 41 | unsafe { 42 | &mut *(self as *mut Self as *mut Self::ByteReader) 43 | } 44 | 45 | } 46 | } 47 | 48 | impl ByteReader for Reader { 49 | type BitReader = Reader; 50 | /// read the next byte of data at the current offset. 51 | /// the output data is stored in the first 8 bits. 52 | fn read_byte(&mut self) -> u8 { 53 | assert!(self.num_remaining_bits > 8, "Attempt to read beyond buffer bounds."); 54 | debug_assert!(self.pos_in_curr_byte == 0, "Cannot read byte when not at the start of a byte."); 55 | let value = unsafe { ptr::read(self.ptr) }; 56 | self.ptr = unsafe { self.ptr.add(1) }; 57 | self.num_remaining_bits -= 8; 58 | value 59 | } 60 | 61 | fn into_bit_reader(&mut self) -> &mut Self::BitReader { 62 | // Safety: we forced the memory layout of `Reader` and `Reader` to be the same. 63 | unsafe { 64 | &mut *(self as *mut Self as *mut Self::BitReader) 65 | } 66 | } 67 | } 68 | 69 | impl Reader { 70 | /// read the 'size' bits of data at the current offset without checking the bounds. 71 | /// the output data is stored in the first 'size' bits. 72 | /// Safety: The caller must ensure that 73 | 74 | /// (1) 'size' is less than or equal to 'self.num_remaining_bits'. 75 | 76 | /// (1) 'size' is less than or equal to 'self.num_remaining_bits'. 77 | /// (2) 'size' is less than or equal to 64. 78 | pub unsafe fn read_bits_unchecked(&mut self, size: u8) -> u64 { 79 | self.num_remaining_bits = self.num_remaining_bits.unchecked_sub(size as usize); 80 | 81 | let mut offset = if Order::IS_MSB_FIRST{ size } else { 0 }; 82 | let mut value: u64 = 0; 83 | 84 | if self.pos_in_curr_byte != 0 { 85 | let num_remaining_in_curr_byte = 8 - self.pos_in_curr_byte; 86 | if size <= num_remaining_in_curr_byte { 87 | value = unsafe { 88 | if Order::IS_MSB_FIRST { 89 | (ptr::read(self.ptr) & ((1<> (num_remaining_in_curr_byte.unchecked_sub(size)) 90 | } else { 91 | ptr::read(self.ptr) >> self.pos_in_curr_byte 92 | } 93 | } as u64; 94 | self.pos_in_curr_byte = if size == num_remaining_in_curr_byte { 95 | self.ptr = unsafe{ self.ptr.add(1) }; 96 | 0 97 | } else { 98 | self.pos_in_curr_byte.unchecked_add(size) 99 | }; 100 | return value&((1<> self.pos_in_curr_byte) as usize 107 | } 108 | } as u64; 109 | self.ptr = unsafe{ self.ptr.add(1) }; 110 | offset = if Order::IS_MSB_FIRST { 111 | offset.unchecked_sub(num_remaining_in_curr_byte) 112 | } else { 113 | num_remaining_in_curr_byte 114 | }; 115 | } 116 | 117 | 118 | for _ in 0..if Order::IS_MSB_FIRST{ offset } else { size.unchecked_sub(offset) } >> 3 { 119 | if Order::IS_MSB_FIRST { 120 | offset = offset.unchecked_sub(8); 121 | } 122 | value |= unsafe{ ptr::read(self.ptr) as u64 } << offset; 123 | if !Order::IS_MSB_FIRST { 124 | offset = offset.unchecked_add(8); 125 | } 126 | self.ptr = unsafe{ self.ptr.add(1) }; 127 | } 128 | 129 | // 'size'-'offset' is the number of bits remaining to be read. 130 | value |= unsafe{ 131 | if Order::IS_MSB_FIRST { 132 | (ptr::read(self.ptr) as u64 >> (8_u8.unchecked_sub(offset))) & ((1< Reader { 148 | pub(super) fn new(buffer: RawBuffer, len: usize) -> Self { 149 | let ptr = buffer.data.as_ptr(); 150 | Self { 151 | ptr, 152 | num_remaining_bits: len, 153 | pos_in_curr_byte: 0, 154 | _buffer: buffer, 155 | _phantom: std::marker::PhantomData, 156 | } 157 | } 158 | 159 | 160 | pub fn get_num_remaining_bits(&self) -> usize { 161 | self.num_remaining_bits 162 | } 163 | } -------------------------------------------------------------------------------- /draco-oxide/src/core/corner_table/all_inclusive_corner_table.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{ 2 | corner_table::{ 3 | attribute_corner_table::AttributeCornerTable, 4 | CornerTable, 5 | GenericCornerTable 6 | }, 7 | shared::{AttributeValueIdx, CornerIdx, FaceIdx, VecVertexIdx} 8 | }; 9 | 10 | 11 | /// All-inclusive corner table that contains the universal corner table and the attribute corner tables (if any). 12 | /// This structure is constructed as a return value of the edgebreaker connectivity encoding, and will be passed to 13 | /// the attribute encoder for read-access. 14 | #[derive(Debug, Clone)] 15 | pub(crate) struct AllInclusiveCornerTable<'faces> { 16 | universal: CornerTable<'faces>, 17 | attribute_tables: Vec, 18 | } 19 | 20 | impl<'faces> AllInclusiveCornerTable<'faces> { 21 | pub fn new( 22 | universal: CornerTable<'faces>, 23 | attribute_tables: Vec, 24 | ) -> Self { 25 | Self { 26 | universal, 27 | attribute_tables, 28 | } 29 | } 30 | 31 | pub fn attribute_corner_table<'table>( 32 | &'table self, 33 | idx: usize, 34 | ) -> Option> { 35 | if idx>0 { 36 | let idx = idx - 1; 37 | if idx < self.attribute_tables.len() { 38 | Some(RefAttributeCornerTable::new(idx, self)) 39 | } else { 40 | None 41 | } 42 | } else { 43 | None 44 | } 45 | } 46 | 47 | pub fn universal_corner_table(&self) -> &CornerTable<'faces> { 48 | &self.universal 49 | } 50 | } 51 | 52 | 53 | /// Reference to an attribute corner table. 54 | /// Mostly used to read-access the attribute corner table when encoding attributes. 55 | #[derive(Debug, Clone)] 56 | pub(crate) struct RefAttributeCornerTable<'faces, 'table> { 57 | idx: usize, 58 | corner_table: &'table AllInclusiveCornerTable<'faces>, 59 | } 60 | 61 | impl<'faces, 'table> RefAttributeCornerTable<'faces, 'table> { 62 | pub fn new( 63 | idx: usize, 64 | corner_table: &'table AllInclusiveCornerTable<'faces>, 65 | ) -> Self { 66 | Self { idx, corner_table } 67 | } 68 | } 69 | 70 | impl<'faces, 'table> GenericCornerTable for RefAttributeCornerTable<'faces, 'table> { 71 | fn face_idx_containing(&self, corner: CornerIdx) -> FaceIdx { 72 | // The face index is the same as in the universal corner table 73 | self.corner_table.universal.face_idx_containing(corner) 74 | } 75 | 76 | fn num_faces(&self) -> usize { 77 | // number of faces is the same as the number of faces in the universal corner table 78 | self.corner_table.universal.num_faces() 79 | } 80 | 81 | fn num_corners(&self) -> usize { 82 | // number of corners is the same as the number of corners in the universal corner table 83 | self.corner_table.universal.num_corners() 84 | } 85 | fn num_vertices(&self) -> usize { 86 | self.corner_table.attribute_tables.get(self.idx).unwrap().num_vertices() 87 | } 88 | fn point_idx(&self, corner: CornerIdx) -> crate::core::shared::PointIdx { 89 | self.corner_table.universal_corner_table().point_idx(corner) 90 | } 91 | fn vertex_idx(&self, corner: CornerIdx) -> crate::core::shared::VertexIdx { 92 | self.corner_table.attribute_tables.get(self.idx).unwrap().vertex_idx(corner) 93 | } 94 | fn next(&self, c: CornerIdx) -> CornerIdx { 95 | self.corner_table.attribute_tables.get(self.idx).unwrap().next(c, &self.corner_table.universal) 96 | } 97 | fn previous(&self, c: CornerIdx) -> CornerIdx { 98 | self.corner_table.attribute_tables.get(self.idx).unwrap().previous(c, &self.corner_table.universal) 99 | } 100 | fn opposite(&self, c: CornerIdx) -> Option { 101 | self.corner_table.attribute_tables.get(self.idx).unwrap().opposite(c, &self.corner_table.universal) 102 | } 103 | fn left_most_corner(&self, vertex: crate::core::shared::VertexIdx) -> CornerIdx { 104 | self.corner_table.attribute_tables.get(self.idx).unwrap().left_most_corner(vertex) 105 | } 106 | fn vertex_to_attribute_map(&self) -> Option<&VecVertexIdx> { 107 | self.corner_table.attribute_tables.get(self.idx).map(|att| att.get_vertex_to_attribute_map()) 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /draco-oxide/src/core/mesh/meh_features.rs: -------------------------------------------------------------------------------- 1 | use crate::core::texture::TextureMap; 2 | 3 | #[derive(Clone, Debug)] 4 | pub(crate) struct MeshFeatures { 5 | label: String, 6 | feature_count: i32, 7 | null_feature_id: i32, 8 | attribute_index: i32, 9 | texture_map: TextureMap, 10 | texture_channels: Vec, 11 | property_table_index: i32, 12 | } 13 | 14 | impl MeshFeatures { 15 | pub fn new() -> Self { 16 | Self { 17 | label: String::new(), 18 | feature_count: 0, 19 | null_feature_id: -1, 20 | attribute_index: -1, 21 | texture_map: TextureMap::new(), 22 | texture_channels: Vec::new(), 23 | property_table_index: -1, 24 | } 25 | } 26 | 27 | pub fn set_label(&mut self, label: &str) { 28 | self.label = label.to_owned(); 29 | } 30 | 31 | pub fn get_label(&self) -> &str { 32 | &self.label 33 | } 34 | 35 | pub fn set_feature_count(&mut self, count: i32) { 36 | self.feature_count = count; 37 | } 38 | 39 | pub fn get_feature_count(&self) -> i32 { 40 | self.feature_count 41 | } 42 | 43 | pub fn set_null_feature_id(&mut self, id: i32) { 44 | self.null_feature_id = id; 45 | } 46 | 47 | pub fn get_null_feature_id(&self) -> i32 { 48 | self.null_feature_id 49 | } 50 | 51 | pub fn set_attribute_index(&mut self, index: i32) { 52 | self.attribute_index = index; 53 | } 54 | 55 | pub fn get_attribute_index(&self) -> i32 { 56 | self.attribute_index 57 | } 58 | 59 | pub fn set_texture_map(&mut self, map: &TextureMap) { 60 | self.texture_map = map.clone(); 61 | } 62 | 63 | pub fn get_texture_map(&self) -> &TextureMap { 64 | &self.texture_map 65 | } 66 | 67 | pub fn set_texture_channels(&mut self, channels: &[i32]) { 68 | self.texture_channels = channels.to_vec(); 69 | } 70 | 71 | pub fn set_property_table_index(&mut self, index: i32) { 72 | self.property_table_index = index; 73 | } 74 | 75 | pub fn get_property_table_index(&self) -> i32 { 76 | self.property_table_index 77 | } 78 | } -------------------------------------------------------------------------------- /draco-oxide/src/core/mesh/metadata.rs: -------------------------------------------------------------------------------- 1 | #[derive(Debug, Clone, PartialEq, Eq, Default)] 2 | pub struct Metadata { 3 | entries: std::collections::HashMap, 4 | } 5 | 6 | impl Metadata { 7 | pub fn new() -> Self { 8 | Self { 9 | entries: std::collections::HashMap::new(), 10 | } 11 | } 12 | 13 | pub fn add_entry(&mut self, key: String, value: String) { 14 | self.entries.insert(key, value); 15 | } 16 | 17 | pub fn get_entry(&self, key: &str) -> Option<&String> { 18 | self.entries.get(key) 19 | } 20 | } -------------------------------------------------------------------------------- /draco-oxide/src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod attribute; 2 | pub(crate) mod bit_coder; 3 | pub(crate) mod buffer; 4 | pub mod mesh; 5 | pub(crate) mod point_cloud; 6 | pub(crate) mod point_cloud_builder; 7 | pub mod shared; 8 | pub(crate) mod corner_table; 9 | pub(crate) mod texture; 10 | pub(crate) mod scene; 11 | pub(crate) mod material; 12 | pub(crate) mod structural_metadata; -------------------------------------------------------------------------------- /draco-oxide/src/core/point_cloud/mod.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/src/core/point_cloud/mod.rs -------------------------------------------------------------------------------- /draco-oxide/src/core/point_cloud_builder/mod.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/src/core/point_cloud_builder/mod.rs -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/inverse_prediction_transform/difference.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::Vector; 2 | use crate::decode::attribute::portabilization::{ 3 | Deportabilization, 4 | DeportabilizationImpl 5 | }; 6 | use crate::prelude::ByteReader; 7 | use crate::shared::attribute::Portable; 8 | 9 | use super::InversePredictionTransformImpl; 10 | 11 | 12 | pub(crate) struct DifferenceInverseTransform 13 | where Data: Vector + Portable 14 | { 15 | metadata: Data, 16 | deportabilization: Deportabilization<::Correction>, 17 | } 18 | 19 | impl InversePredictionTransformImpl for DifferenceInverseTransform 20 | where Data: Vector + Portable 21 | { 22 | type Data = Data; 23 | type Correction = Data; 24 | type Metadata = Data; 25 | 26 | const ID: usize = 1; 27 | 28 | fn new(reader: &mut R) -> Result 29 | where R: ByteReader 30 | { 31 | let metadata = Data::read_from(reader).unwrap(); // TODO: handle error properly 32 | let deportabilization = Deportabilization::new(reader)?; 33 | Ok ( 34 | Self { 35 | metadata, 36 | deportabilization, 37 | } 38 | ) 39 | } 40 | 41 | fn inverse( 42 | &self, 43 | pred: Self::Data, 44 | reader: &mut R, 45 | ) -> Self::Data 46 | where R: ByteReader 47 | { 48 | let corr = self.deportabilization.deportabilize_next(reader); 49 | pred + corr + self.metadata 50 | } 51 | } 52 | 53 | 54 | // #[cfg(test)] 55 | // mod tests { 56 | // use super::*; 57 | // use crate::core::shared::NdVector; 58 | // use crate::encode::attribute::portabilization; 59 | // use crate::encode::attribute::prediction_transform::FinalMetadata; 60 | // use crate::encode::attribute::prediction_transform::{ 61 | // self, 62 | // PredictionTransformImpl 63 | // }; 64 | // use crate::decode::attribute::prediction_inverse_transform::InversePredictionTransformImpl; 65 | // use crate::core::shared::ConfigType; 66 | 67 | // #[test] 68 | // fn test_transform() { 69 | // let mut transform = prediction_transform::difference::Difference::>::new(portabilization::Config::default()); 70 | // let orig1 = NdVector::<3, f64>::from([1.0, 2.0, 3.0]); 71 | // let pred1 = NdVector::<3, f64>::from([1.0, 1.0, 1.0]); 72 | // let orig2 = NdVector::<3, f64>::from([4.0, 5.0, 6.0]); 73 | // let pred2 = NdVector::<3, f64>::from([5.0, 5.0, 5.0]); 74 | 75 | // transform.map_with_tentative_metadata(orig1.clone(), pred1.clone()); 76 | // transform.map_with_tentative_metadata(orig2.clone(), pred2.clone()); 77 | 78 | // transform.squeeze(); 79 | // let final_metadata = match transform.get_final_metadata() { 80 | // FinalMetadata::Local(_) => panic!("Expected global metadata"), 81 | // FinalMetadata::Global(m) => m, 82 | // }; 83 | // let metadata = NdVector::<3, f64>::from([-1.0, 0.0, 1.0]); 84 | // assert_eq!(final_metadata, &metadata); 85 | 86 | // let mut inverse = DifferenceInverseTransform::>::new(); 87 | // inverse.init(*final_metadata); 88 | // let recovered1 = inverse.inverse(pred1.clone(), transform.get_corr_as_slice()[0]); 89 | // let recovered2 = inverse.inverse(pred2.clone(), transform.get_corr_as_slice()[1]); 90 | // assert_eq!(recovered1, orig1); 91 | // assert_eq!(recovered2, orig2); 92 | // } 93 | // } -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/inverse_prediction_transform/oct_difference.rs: -------------------------------------------------------------------------------- 1 | use super::InversePredictionTransformImpl; 2 | use crate::decode::attribute::portabilization::{Deportabilization, DeportabilizationImpl}; 3 | use crate::encode::attribute::prediction_transform::geom::{ 4 | octahedral_inverse_transform, 5 | octahedral_transform 6 | }; 7 | use crate::core::shared::{ 8 | NdVector, Vector 9 | }; 10 | use crate::prelude::ByteReader; 11 | 12 | pub struct OctahedronDifferenceInverseTransform 13 | where Data: Vector, 14 | { 15 | _marker: std::marker::PhantomData, 16 | deportabilization: Deportabilization<::Correction>, 17 | } 18 | 19 | impl InversePredictionTransformImpl for OctahedronDifferenceInverseTransform 20 | where 21 | Data: Vector, 22 | { 23 | const ID: usize = 2; 24 | 25 | type Data = Data; 26 | type Correction = NdVector<2,f64>; 27 | type Metadata = (); 28 | 29 | fn new(reader: &mut R) -> Result 30 | where R: ByteReader 31 | { 32 | let deportabilization = Deportabilization::new(reader)?; 33 | Ok( 34 | Self { 35 | _marker: std::marker::PhantomData, 36 | deportabilization, 37 | } 38 | ) 39 | } 40 | 41 | fn inverse(&self, pred: Self::Data, reader: &mut R) -> Self::Data 42 | where R: ByteReader 43 | { 44 | let crr = self.deportabilization.deportabilize_next(reader); 45 | // Safety: 46 | // We made sure that the data is three dimensional. 47 | debug_assert!( 48 | Data::NUM_COMPONENTS == 3, 49 | ); 50 | 51 | let pred_in_oct = unsafe { 52 | octahedral_transform(pred) 53 | }; 54 | 55 | let orig = pred_in_oct + crr; 56 | 57 | // Safety: 58 | // We made sure that the data is three dimensional. 59 | unsafe { 60 | octahedral_inverse_transform(orig) 61 | } 62 | } 63 | } 64 | 65 | 66 | // #[cfg(test)] 67 | // mod tests { 68 | // use super::*; 69 | // use crate::core::shared::NdVector; 70 | // use crate::encode::attribute::portabilization; 71 | // use crate::encode::attribute::prediction_transform::oct_difference::OctahedronDifferenceTransform; 72 | // use crate::encode::attribute::prediction_transform::PredictionTransformImpl; 73 | // use crate::core::shared::ConfigType; 74 | 75 | // #[test] 76 | // fn test_transform() { 77 | // let mut transform = OctahedronDifferenceTransform::>::new(portabilization::Config::default()); 78 | // let orig1 = NdVector::<3, f64>::from([1.0, 2.0, 3.0]).normalize(); 79 | // let pred1 = NdVector::<3, f64>::from([1.0, 1.0, 1.0]).normalize(); 80 | // let orig2 = NdVector::<3, f64>::from([4.0, 5.0, 6.0]).normalize(); 81 | // let pred2 = NdVector::<3, f64>::from([5.0, 5.0, 5.0]).normalize(); 82 | 83 | // transform.map_with_tentative_metadata(orig1.clone(), pred1.clone()); 84 | // transform.map_with_tentative_metadata(orig2.clone(), pred2.clone()); 85 | 86 | // transform.squeeze(); 87 | // let mut inverse = OctahedronDifferenceInverseTransform::>::new(); 88 | // let recovered1 = inverse.inverse(pred1.clone(), transform.get_corr()[0]); 89 | // let recovered2 = inverse.inverse(pred2.clone(), transform.get_corr()[1]); 90 | // assert!((recovered1 - orig1).norm() < 0.000_000_1); 91 | // assert!((recovered2 - orig2).norm() < 0.000_000_1); 92 | // } 93 | // } -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/inverse_prediction_transform/oct_orthogonal.rs: -------------------------------------------------------------------------------- 1 | use super::InversePredictionTransformImpl; 2 | use crate::core::shared::{DataValue, NdVector, Vector}; 3 | use crate::decode::attribute::portabilization::Deportabilization; 4 | use crate::prelude::ByteReader; 5 | use crate::shared::attribute::Portable; 6 | 7 | pub(crate) struct OctahedronOrthogonalInverseTransform 8 | where Data: Vector + Portable, 9 | { 10 | deportabilization: Deportabilization<::Correction>, 11 | _marker: std::marker::PhantomData, 12 | } 13 | 14 | impl InversePredictionTransformImpl for OctahedronOrthogonalInverseTransform 15 | where 16 | Data: Vector + Portable, 17 | Data::Component: DataValue 18 | { 19 | const ID: usize = 3; 20 | 21 | type Data = Data; 22 | type Correction = NdVector<2,f64>; 23 | type Metadata = (); 24 | 25 | fn new(reader: &mut R) -> Result 26 | where R: ByteReader 27 | { 28 | let deportabilization = Deportabilization::new(reader)?; 29 | Ok( 30 | Self { 31 | _marker: std::marker::PhantomData, 32 | deportabilization, 33 | } 34 | ) 35 | } 36 | 37 | fn inverse(&self, mut pred: Self::Data, reader: &mut R) -> Self::Data 38 | where R: ByteReader 39 | { 40 | unimplemented!(); 41 | 42 | // // Safety: 43 | // // We made sure that the data is three dimensional. 44 | // debug_assert!( 45 | // Data::NUM_COMPONENTS == 3, 46 | // ); 47 | 48 | // let mut reflected = false; 49 | // let quadrant; 50 | // unsafe { 51 | // if *pred.get_unchecked(2) < Data::Component::zero() { 52 | // reflected = true; 53 | // let minus_one = Data::Component::from_f64(-1.0); 54 | // *pred.get_unchecked_mut(2) *= minus_one; 55 | // } 56 | 57 | // if *pred.get_unchecked(0) > Data::Component::zero() { 58 | // if *pred.get_unchecked(1) > Data::Component::zero() { 59 | // // first quadrant. Rotate around z-axis by pi. 60 | // quadrant = 1; 61 | // let minus_one = Data::Component::from_f64(-1.0); 62 | // *pred.get_unchecked_mut(0) *= minus_one; 63 | // *pred.get_unchecked_mut(1) *= minus_one; 64 | // } else { 65 | // // fourth quadrant. Rotate around z-axis by -pi/2. 66 | // quadrant = 4; 67 | // let temp = *pred.get_unchecked(0); 68 | // let one = Data::Component::one(); 69 | // let minus_one = Data::Component::zero() - one; 70 | // *pred.get_unchecked_mut(0) = *pred.get_unchecked(1); 71 | // *pred.get_unchecked_mut(1) = temp * minus_one; 72 | // } 73 | // } else { 74 | // if *pred.get_unchecked(1) > Data::Component::zero() { 75 | // // second quadrant. Rotate around z-axis by pi/2. 76 | // quadrant = 2; 77 | // let temp = *pred.get_unchecked(0); 78 | // let one = Data::Component::one(); 79 | // let minus_one = Data::Component::zero() - one; 80 | // *pred.get_unchecked_mut(0) = *pred.get_unchecked(1) * minus_one; 81 | // *pred.get_unchecked_mut(1) = temp; 82 | // } else { 83 | // // third quadrant will not be transformed. 84 | // quadrant = 3; 85 | // } 86 | // }; 87 | // } 88 | 89 | // let pred_in_oct = unsafe { 90 | // octahedral_transform(pred) 91 | // }; 92 | 93 | // let orig = pred_in_oct + crr; 94 | // unsafe{ 95 | // if *pred.get_unchecked(2) < Data::Component::zero() { 96 | // let minus_one = Data::Component::from_f64(-1.0); 97 | // *pred.get_unchecked_mut(2) *= minus_one; 98 | // } 99 | // } 100 | 101 | // // Safety: 102 | // // We made sure that the data is three dimensional. 103 | // let mut orig: Data = unsafe { 104 | // octahedral_inverse_transform(orig) 105 | // }; 106 | 107 | // // pull back the transformation 108 | // // Safety: 109 | // // We made sure that the data is three dimensional. 110 | // unsafe { 111 | // if quadrant == 1 { 112 | // let minus_one = Data::Component::from_f64(-1.0); 113 | // *orig.get_unchecked_mut(0) *= minus_one; 114 | // *orig.get_unchecked_mut(1) *= minus_one; 115 | // } else if quadrant == 2 { 116 | // // rotate around z-axis by -pi/2 117 | // let temp = *orig.get_unchecked(0); 118 | // let one = Data::Component::one(); 119 | // let minus_one = Data::Component::zero() - one; 120 | // *orig.get_unchecked_mut(0) = *orig.get_unchecked(1) * minus_one; 121 | // *orig.get_unchecked_mut(1) = temp; 122 | // } else if quadrant == 4 { 123 | // // rotate around z-axis by pi/2 124 | // let temp = *orig.get_unchecked(0); 125 | // let one = Data::Component::one(); 126 | // let minus_one = Data::Component::zero() - one; 127 | // *orig.get_unchecked_mut(0) = *orig.get_unchecked(1); 128 | // *orig.get_unchecked_mut(1) = temp * minus_one; 129 | // } 130 | 131 | // if reflected { 132 | // let minus_one = Data::Component::from_f64(-1.0); 133 | // *orig.get_unchecked_mut(2) *= minus_one; 134 | // } 135 | // } 136 | 137 | // orig 138 | } 139 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/inverse_prediction_transform/oct_reflection.rs: -------------------------------------------------------------------------------- 1 | use std::result::Result; 2 | 3 | use super::InversePredictionTransformImpl; 4 | use crate::core::shared::{DataValue, NdVector, Vector}; 5 | use crate::decode::attribute::portabilization::{Deportabilization, DeportabilizationImpl}; 6 | use crate::encode::attribute::prediction_transform::geom::*; 7 | use crate::prelude::ByteReader; 8 | use crate::shared::attribute::Portable; 9 | 10 | pub(crate) struct OctahedronReflectionInverseTransform 11 | where Data: Vector + Portable, 12 | { 13 | metadata: Data, 14 | deportabilization: Deportabilization<::Correction>, 15 | } 16 | 17 | impl InversePredictionTransformImpl for OctahedronReflectionInverseTransform 18 | where 19 | Data: Vector + Portable, 20 | Data::Component: DataValue 21 | { 22 | type Data = Data; 23 | type Correction = NdVector<2,f64>; 24 | type Metadata = Data; 25 | 26 | const ID: usize = 4; 27 | 28 | fn new(reader: &mut R) -> Result 29 | where R: ByteReader 30 | { 31 | let deportabilization = Deportabilization::new(reader)?; 32 | let metadata = Data::read_from(reader)?; 33 | Ok( 34 | Self { 35 | metadata, 36 | deportabilization, 37 | } 38 | ) 39 | } 40 | 41 | fn inverse(&self, mut pred: Self::Data, reader: &mut R) -> Self::Data 42 | where R: ByteReader 43 | { 44 | let crr = self.deportabilization.deportabilize_next(reader); 45 | // Safety: 46 | // We made sure that the data is three dimensional. 47 | debug_assert!( 48 | Data::NUM_COMPONENTS == 3, 49 | ); 50 | 51 | let pred_lies_in_upper_half = unsafe { 52 | *pred.get_unchecked(2) > Data::Component::zero() 53 | }; 54 | 55 | if pred_lies_in_upper_half { 56 | let minus_one = Data::Component::from_f64(-1.0); 57 | unsafe{ *pred.get_unchecked_mut(2) *= minus_one; } 58 | } 59 | 60 | let pred_in_oct = unsafe { 61 | octahedral_transform(pred) 62 | }; 63 | 64 | let orig = pred_in_oct + crr; 65 | unsafe{ 66 | if *pred.get_unchecked(2) < Data::Component::zero() { 67 | let minus_one = Data::Component::from_f64(-1.0); 68 | *pred.get_unchecked_mut(2) *= minus_one; 69 | } 70 | } 71 | 72 | // Safety: 73 | // We made sure that the data is three dimensional. 74 | unsafe { 75 | octahedral_inverse_transform(orig) 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/inverse_prediction_transform/orthogonal.rs: -------------------------------------------------------------------------------- 1 | use super::InversePredictionTransformImpl; 2 | use crate::core::shared::{ 3 | DataValue, 4 | NdVector, 5 | Vector, 6 | }; 7 | use crate::decode::attribute::portabilization::{Deportabilization, DeportabilizationImpl}; 8 | use crate::encode::attribute::prediction_transform::geom::*; 9 | use crate::prelude::ByteReader; 10 | use crate::shared::attribute::Portable; 11 | 12 | pub(crate) struct OrthogonalInverseTransform 13 | where Data: Vector, 14 | { 15 | metadata: bool, 16 | deportabilization: Deportabilization<::Correction>, 17 | _phantom: std::marker::PhantomData, 18 | } 19 | 20 | impl InversePredictionTransformImpl for OrthogonalInverseTransform 21 | where 22 | Data: Vector, 23 | Data::Component: DataValue, 24 | { 25 | type Data = Data; 26 | type Correction = NdVector<2,f64>; 27 | type Metadata = bool; 28 | 29 | const ID: usize = 5; 30 | 31 | fn new(reader: &mut R) -> Result 32 | where R: ByteReader 33 | { 34 | let deportabilization = Deportabilization::new(reader)?; 35 | let metadata = ::read_from(reader)?; 36 | 37 | Ok( 38 | Self { 39 | metadata, 40 | deportabilization, 41 | _phantom: std::marker::PhantomData, 42 | } 43 | ) 44 | } 45 | 46 | fn inverse(&self, pred: Self::Data, reader: &mut R) -> Self::Data 47 | where R: ByteReader 48 | { 49 | let crr = self.deportabilization.deportabilize_next(reader); 50 | 51 | let one = Data::Component::one(); 52 | let pred_norm_squared = pred.dot(pred); 53 | let ref_on_pred_perp = if self.metadata { 54 | // Safety: 55 | // dereferencing the constant-sized array by a constant index 56 | unsafe { 57 | let mut out = pred * (*pred.get_unchecked(0) / pred_norm_squared); 58 | *out.get_unchecked_mut(0) += one; 59 | out 60 | } 61 | } else { 62 | // Safety: 63 | // dereferencing the constant-sized array by a constant index 64 | unsafe { 65 | let mut out = pred * (*pred.get_unchecked(1) / pred_norm_squared); 66 | *out.get_unchecked_mut(1) += one; 67 | out 68 | } 69 | }; 70 | 71 | let mut pred_cross_orig = Data::zero(); 72 | let rotation = rotation_matrix_from(pred, unsafe{ *crr.get_unchecked(0) }); 73 | unsafe { 74 | *pred_cross_orig.get_unchecked_mut(0) = rotation.get_unchecked(0).dot(ref_on_pred_perp); 75 | *pred_cross_orig.get_unchecked_mut(1) = rotation.get_unchecked(1).dot(ref_on_pred_perp); 76 | *pred_cross_orig.get_unchecked_mut(2) = rotation.get_unchecked(2).dot(ref_on_pred_perp); 77 | }; 78 | 79 | // now recover the original vector by rotating 'pred' on the plane defined by 'pred_cross_orig' 80 | let rotation = rotation_matrix_from(pred_cross_orig, unsafe{ *crr.get_unchecked(1) }); 81 | let mut orig = Data::zero(); 82 | unsafe { 83 | *orig.get_unchecked_mut(0) = rotation.get_unchecked(0).dot(pred); 84 | *orig.get_unchecked_mut(1) = rotation.get_unchecked(1).dot(pred); 85 | *orig.get_unchecked_mut(2) = rotation.get_unchecked(2).dot(pred); 86 | }; 87 | 88 | orig 89 | } 90 | } 91 | 92 | -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod attribute_decoder; 2 | pub(crate) mod inverse_prediction_transform; 3 | pub(crate) mod portabilization; 4 | 5 | use thiserror::Error; 6 | use crate::{core::bit_coder::ReaderErr, decode::header::Header, prelude::{Attribute, ByteReader, ConfigType}, shared::{attribute::{self, AttributeKind}, header::EncoderMethod}, utils::bit_coder::leb128_read}; 7 | 8 | 9 | #[derive(Debug, Error)] 10 | pub enum Err { 11 | #[error("Attribute error: {0}")] 12 | AttributeDecoderError(#[from] attribute_decoder::Err), 13 | #[error("Prediction inverse transform error: {0}")] 14 | PredictionInverseTransformError(String), 15 | #[error("Not Enough Data: {0}")] 16 | NotEnoughData(#[from] ReaderErr), 17 | #[error("Attribute Error: {0}")] 18 | AttributeError(#[from] attribute::Err), 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | pub struct Config { 23 | decoder_cfgs: Vec, 24 | } 25 | 26 | impl ConfigType for Config { 27 | fn default() -> Self { 28 | Self { 29 | decoder_cfgs: vec![attribute_decoder::Config::default()], 30 | } 31 | } 32 | } 33 | 34 | pub fn decode_attributes( 35 | reader: &mut W, 36 | _cfg: Config, 37 | header: Header, 38 | mut decoded_attributes: Vec, 39 | ) -> Result, Err> 40 | where W: ByteReader, 41 | { 42 | // Read the number of attributes 43 | let num_att_decs = reader.read_u8().unwrap() as usize; 44 | 45 | let mut att_dec_data_id = Vec::new(); 46 | let mut att_dec_type = Vec::new(); 47 | let mut att_dec_traversal_method = Vec::new(); 48 | if header.encoding_method == EncoderMethod::Edgebreaker { 49 | for i in 0..num_att_decs { 50 | att_dec_data_id.push(reader.read_u8()?); 51 | att_dec_type.push(AttributeKind::read_from(reader)?); 52 | att_dec_traversal_method.push(reader.read_u8()?); 53 | } 54 | } 55 | 56 | let mut att_dec_num_attributes = Vec::with_capacity(num_att_decs); 57 | let mut att_dec_att_types = Vec::with_capacity(num_att_decs); 58 | let mut att_dec_data_types = Vec::with_capacity(num_att_decs); 59 | let mut att_dec_num_components = Vec::with_capacity(num_att_decs); 60 | let mut att_dec_normalized = Vec::with_capacity(num_att_decs); 61 | let mut att_dec_unique_ids = Vec::with_capacity(num_att_decs); 62 | let mut seq_att_dec_decoder_type = Vec::with_capacity(num_att_decs); 63 | for i in 0.. num_att_decs { 64 | att_dec_num_attributes.push( leb128_read(reader)? as usize); 65 | att_dec_att_types.push(Vec::with_capacity(att_dec_num_attributes[i])); 66 | att_dec_data_types.push(Vec::with_capacity(att_dec_num_attributes[i])); 67 | att_dec_num_components.push(Vec::with_capacity(att_dec_num_attributes[i])); 68 | att_dec_normalized.push(Vec::with_capacity(att_dec_num_attributes[i])); 69 | att_dec_unique_ids.push(Vec::with_capacity(att_dec_num_attributes[i])); 70 | for j in 0..att_dec_num_attributes[i] { 71 | att_dec_att_types[i][j] = AttributeKind::read_from(reader)?; 72 | att_dec_data_types[i][j] = reader.read_u8()?; 73 | att_dec_num_components[i][j] = reader.read_u8()?; 74 | att_dec_normalized[i][j] = reader.read_u8()?; 75 | att_dec_unique_ids[i][j] = leb128_read(reader)?; 76 | } 77 | seq_att_dec_decoder_type.push(Vec::with_capacity(att_dec_num_attributes[i])); 78 | for j in 0..att_dec_num_attributes[i] { 79 | seq_att_dec_decoder_type[i][j] = reader.read_u8()?; 80 | } 81 | } 82 | 83 | let mut vertex_visited_point_ids = vec![0; num_att_decs as usize]; 84 | let mut curr_att_dec = 0; 85 | 86 | // if header.encoding_method == EncoderMethod::Edgebreaker { 87 | // decode_attribute_seams(reader)?; 88 | // for i in 0..num_encoded_vertices + num_encoded_split_symbols { 89 | // if is_vert_hole_[i] { 90 | // update_vertex_to_corner_map(i); 91 | // } 92 | // } 93 | // for i in 1..num_att_decs { 94 | // curr_att_dec = i; 95 | // recompute_vertices_internal(); 96 | // } 97 | // attribute_assign_points_to_corners(); 98 | // } 99 | // for i in 0..num_att_decs { 100 | // curr_att_dec = i; 101 | // is_face_visited_.assign(num_faces, false); 102 | // is_vertex_visited_.assign(num_faces * 3, false); 103 | // generate_sequence(); 104 | // if header.encoding_method == EncoderMethod::Edgebreaker { 105 | // update_point_to_attribute_index_mapping(); 106 | // } 107 | // } 108 | // for i in 0..num_att_decs { 109 | // for j in 0..att_dec_num_attributes[i] { 110 | // att_dec_num_values_to_decode[i][j] = encoded_attribute_value_index_to_corner_map[i].size(); 111 | // } 112 | // } 113 | // for i in 0..num_att_decs { 114 | // curr_att_dec = i; 115 | // decode_portable_attributes(); 116 | // decode_ata_needed_by_portable_transforms(); 117 | // transform_attributes_to_original_format(); 118 | // } 119 | 120 | Ok(decoded_attributes) 121 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/portabilization/dequantization_rect_array.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::shared::DataValue, prelude::{ByteReader, Vector}, shared::attribute::Portable}; 2 | use super::DeportabilizationImpl; 3 | 4 | pub(crate) struct DequantizationRectangleArray { 5 | global_metadata_min: Data, 6 | quantization_size: Data, 7 | range: Data, 8 | } 9 | 10 | impl DequantizationRectangleArray 11 | where 12 | Data: Vector + Portable, 13 | Data::Component: DataValue, 14 | { 15 | pub(crate) fn new(reader: &mut R) -> Self 16 | where R: ByteReader 17 | { 18 | let global_metadata_min = Data::read_from(reader).unwrap(); // TODO: handle error properly 19 | let global_metadata_max = Data::read_from(reader).unwrap(); // TODO: handle error properly 20 | let unit_cube_size = Data::Component::read_from(reader).unwrap(); // TODO: handle error properly; 21 | 22 | // compute the range. This will be multiplied by 1.0001 to avoid the boundary value to overflow. 23 | let range = (global_metadata_max - global_metadata_min) * Data::Component::from_f64(1.0001); 24 | // compute the quantization size 25 | let mut quantization_size = range / unit_cube_size; 26 | for i in 0..Data::NUM_COMPONENTS { 27 | // Safety: Obvious. 28 | unsafe { 29 | *quantization_size.get_unchecked_mut(i) = Data::Component::from_f64( 30 | quantization_size.get_unchecked(i).to_f64().ceil() + 1.0 31 | ); 32 | }; 33 | } 34 | 35 | Self { 36 | global_metadata_min, 37 | quantization_size, 38 | range 39 | } 40 | } 41 | 42 | fn delinearize(&self, reader: &mut R) -> Data 43 | where R: ByteReader 44 | { 45 | Data::read_from(reader).unwrap() // TODO: handle error properly 46 | } 47 | } 48 | 49 | impl DeportabilizationImpl for DequantizationRectangleArray 50 | where 51 | Data: Vector + Portable, 52 | Data::Component: DataValue, 53 | { 54 | fn deportabilize_next(&self, reader: &mut R) -> Data 55 | where R: ByteReader 56 | { 57 | let delinearized = self.delinearize(reader); 58 | let normalized = delinearized.elem_div(self.quantization_size); 59 | let diff = normalized.elem_mul(self.range); 60 | diff + self.global_metadata_min 61 | } 62 | } 63 | 64 | 65 | #[cfg(all(test, not(feature = "evaluation")))] 66 | mod tests { 67 | use crate::core::shared::NdVector; 68 | use crate::decode::attribute::portabilization::Deportabilization; 69 | use crate::encode::attribute::portabilization::{Portabilization, PortabilizationImpl, PortabilizationType, Resolution}; 70 | use crate::encode::attribute::portabilization::Config; 71 | use super::*; 72 | 73 | #[test] 74 | fn test_dequantization_rectangle_array() { 75 | let data = vec![ 76 | NdVector::from([1_f32, -1.0, 1.0]), 77 | NdVector::from([0.7, 0.8, 0.9]), 78 | NdVector::from([0.0, 0.5, 0.0]), 79 | NdVector::from([0.5, 1.0, 0.0]), 80 | ]; 81 | 82 | let cfg = Config { 83 | type_: PortabilizationType::QuantizationRectangleArray, 84 | resolution: Resolution::DivisionSize(1000), 85 | }; 86 | 87 | let mut writer = Vec::new(); 88 | Portabilization::new(data.clone(), cfg, &mut writer) 89 | .portabilize() 90 | .into_iter() 91 | .for_each(|x| x.for_each(|d| d.write_to(&mut writer))); 92 | 93 | let mut reader = writer.into_iter(); 94 | let dequant = Deportabilization::new(&mut reader).unwrap(); 95 | for i in 0..data.len() { 96 | let dequant_data: NdVector<3,f32> = dequant.deportabilize_next(&mut reader); 97 | let err = (dequant_data-data[i]).norm(); 98 | assert!( 99 | err < 1e-2, 100 | "Err too large ({err}). Dequantization failed: expected {:?}, got {:?}", 101 | data[i], dequant_data 102 | ); 103 | } 104 | } 105 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/portabilization/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{debug_expect, prelude::{ByteReader, Vector}, shared::attribute::Portable}; 2 | 3 | pub mod dequantization_rect_array; 4 | pub mod to_bits; 5 | 6 | 7 | #[enum_dispatch::enum_dispatch(DeportabilizationImpl)] 8 | pub(crate) enum Deportabilization 9 | where Data: Vector + Portable, 10 | { 11 | DequantizationRectangleArray(dequantization_rect_array::DequantizationRectangleArray), 12 | ToBits(to_bits::ToBits), 13 | } 14 | 15 | 16 | impl Deportabilization 17 | where Data: Vector + Portable, 18 | { 19 | pub(crate) fn new(reader: &mut R) -> Result 20 | where R: ByteReader 21 | { 22 | debug_expect!("Start of Portabilization Metadata", reader); 23 | let ty = DeportabilizationType::read_from(reader) 24 | .map_err(|id| Err::InvalidDeportabilizationId(id))?; 25 | let out = match ty { 26 | DeportabilizationType::DequantizationRectangleArray => { 27 | Deportabilization::DequantizationRectangleArray(dequantization_rect_array::DequantizationRectangleArray::new(reader)) 28 | }, 29 | DeportabilizationType::ToBits => { 30 | Deportabilization::ToBits(to_bits::ToBits::new(reader)) 31 | } 32 | }; 33 | debug_expect!("End of Portabilization Metadata", reader); 34 | Ok(out) 35 | } 36 | } 37 | 38 | #[enum_dispatch::enum_dispatch] 39 | pub trait DeportabilizationImpl 40 | where Data: Vector + Portable, 41 | { 42 | /// Reads the portabilied data from the buffer and deportablize them. 43 | /// The outputs are (output data, metadata) 44 | fn deportabilize_next(&self, reader: &mut R) -> Data 45 | where R: ByteReader; 46 | } 47 | 48 | 49 | #[remain::sorted] 50 | #[derive(Clone, Copy)] 51 | pub enum DeportabilizationType { 52 | DequantizationRectangleArray, 53 | ToBits, 54 | } 55 | 56 | impl DeportabilizationType { 57 | pub fn read_from(reader: &mut R) -> Result 58 | where R: ByteReader 59 | { 60 | let id = reader.read_u8().unwrap() as usize; // ToDo: handle error properly. 61 | let out = match id { 62 | 0 => DeportabilizationType::DequantizationRectangleArray, 63 | 1 => DeportabilizationType::ToBits, 64 | _ => return Err(id as usize), 65 | }; 66 | Ok(out) 67 | } 68 | } 69 | 70 | #[derive(Debug, thiserror::Error)] 71 | pub enum Err { 72 | #[error("Invalid deportabilization id: {0}")] 73 | InvalidDeportabilizationId(usize), 74 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/attribute/portabilization/to_bits.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::shared::DataValue, prelude::{ByteReader, Vector}, shared::attribute::Portable}; 2 | use super::DeportabilizationImpl; 3 | 4 | pub(crate) struct ToBits { 5 | _marker: std::marker::PhantomData, 6 | } 7 | 8 | impl ToBits 9 | where 10 | Data: Vector + Portable, 11 | Data::Component: DataValue, 12 | { 13 | pub(crate) fn new(_reader: &mut R) -> Self 14 | where R: ByteReader 15 | { 16 | // there is no metadata to read. 17 | Self { 18 | _marker: std::marker::PhantomData, 19 | } 20 | } 21 | } 22 | 23 | impl DeportabilizationImpl for ToBits 24 | where 25 | Data: Vector + Portable, 26 | Data::Component: DataValue, 27 | { 28 | fn deportabilize_next(&self, reader: &mut R) -> Data 29 | where R: ByteReader 30 | { 31 | Data::read_from(reader).unwrap() // TODO: handle error properly 32 | } 33 | } 34 | 35 | 36 | #[cfg(all(test, not(feature = "evaluation")))] 37 | mod tests { 38 | use crate::core::shared::NdVector; 39 | use crate::decode::attribute::portabilization::Deportabilization; 40 | use crate::encode::attribute::portabilization::{Portabilization, PortabilizationImpl, PortabilizationType, Resolution}; 41 | use crate::encode::attribute::portabilization::Config; 42 | use super::*; 43 | 44 | #[test] 45 | fn test() { 46 | let data = vec![ 47 | NdVector::from([1_f32, -1.0, 1.0]), 48 | NdVector::from([0.7, 0.8, 0.9]), 49 | NdVector::from([0.0, 0.5, 0.0]), 50 | NdVector::from([0.5, 1.0, 0.0]), 51 | ]; 52 | 53 | let cfg = Config { 54 | type_: PortabilizationType::ToBits, 55 | resolution: Resolution::DivisionSize(1), // does not matter 56 | }; 57 | 58 | let mut writer = Vec::new(); 59 | Portabilization::new(data.clone(), cfg, &mut writer) 60 | .portabilize() 61 | .into_iter() 62 | .for_each(|x| x.for_each(|d| d.write_to(&mut writer))); 63 | 64 | let mut reader = writer.into_iter(); 65 | let dequant = Deportabilization::new(&mut reader).unwrap(); 66 | for i in 0..data.len() { 67 | let dequant_data: NdVector<3,f32> = dequant.deportabilize_next(&mut reader); 68 | let err = (dequant_data-data[i]).norm(); 69 | assert!( 70 | err < 1e-2, 71 | "Err too large ({err}). Dequantization failed: expected {:?}, got {:?}", 72 | data[i], dequant_data 73 | ); 74 | } 75 | } 76 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/connectivity/mod.rs: -------------------------------------------------------------------------------- 1 | mod sequential; 2 | mod spirale_reversi; 3 | use crate::core::bit_coder::ReaderErr; 4 | use crate::core::shared::{FaceIdx, VertexIdx}; 5 | use crate::debug_expect; 6 | use crate::decode::header::Header; 7 | use crate::prelude::ByteReader; 8 | use crate::shared::connectivity::EdgebreakerDecoder; 9 | use crate::shared::header::EncoderMethod; 10 | 11 | #[derive(Debug, thiserror::Error)] 12 | pub enum Err { 13 | #[error("Sequential decoding error: {0}")] 14 | SequentialError(#[from] sequential::Err), 15 | 16 | #[error("Spirale Reversi decoding error: {0}")] 17 | SpiraleReversiError(#[from] spirale_reversi::Err), 18 | 19 | #[error("Not enough data in stream")] 20 | NotEnoughData(#[from] ReaderErr), 21 | } 22 | 23 | pub fn decode_connectivity_att(reader: &mut R, header: Header) -> Result, Err> 24 | where R: ByteReader, 25 | { 26 | let connectivity = match header.encoding_method { 27 | EncoderMethod::Edgebreaker => { 28 | debug_expect!("Start of edgebreaker connectivity", reader); 29 | let mut decoder = spirale_reversi::SpiraleReversi::new(); 30 | decoder.decode_connectivity(reader)? 31 | }, 32 | EncoderMethod::Sequential => { 33 | debug_expect!("Start of sequential connectivity", reader); 34 | let mut decoder = sequential::Sequential; 35 | decoder.decode_connectivity(reader)? 36 | } 37 | }; 38 | 39 | Ok(connectivity) 40 | } 41 | 42 | 43 | pub trait ConnectivityDecoder { 44 | type Err; 45 | fn decode_connectivity(&mut self, reader: &mut R) -> Result, Self::Err> 46 | where R: ByteReader; 47 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/connectivity/sequential.rs: -------------------------------------------------------------------------------- 1 | use crate::core::bit_coder::ReaderErr; 2 | use crate::debug_expect; 3 | use crate::prelude::ByteReader; 4 | use crate::shared::connectivity::sequential::{index_size_from_vertex_count, Method}; 5 | use crate::core::shared::VertexIdx; 6 | use crate::utils::bit_coder::leb128_read; 7 | use super::ConnectivityDecoder; 8 | 9 | #[derive(thiserror::Error, Debug)] 10 | #[remain::sorted] 11 | pub enum Err { 12 | #[error("Stream input returned with None, though more data was expected.")] 13 | NotEnoughData(#[from] ReaderErr), 14 | } 15 | 16 | pub(crate) struct Sequential; 17 | 18 | 19 | impl ConnectivityDecoder for Sequential { 20 | type Err = Err; 21 | fn decode_connectivity(&mut self, reader: &mut R) -> Result, Err> 22 | where R: ByteReader 23 | { 24 | let num_points = reader.read_u64()?; 25 | let num_faces = reader.read_u64()?; 26 | 27 | let _encoder_method = Method::from_id( 28 | reader.read_u8()? 29 | ); 30 | 31 | let index_size = index_size_from_vertex_count(num_points as usize).unwrap() as u8; 32 | 33 | debug_expect!("Start of indices", reader); 34 | let faces = if index_size == 21 { 35 | // varint decoding 36 | (0..num_faces).map(|_| { 37 | [ 38 | leb128_read(reader).unwrap() as VertexIdx, // ToDo: handle errors properly 39 | leb128_read(reader).unwrap() as VertexIdx, 40 | leb128_read(reader).unwrap() as VertexIdx 41 | ] 42 | }).collect() 43 | } else { 44 | // non-varint decoding 45 | match index_size { 46 | 8 => (0..num_faces).map(|_| { 47 | [ 48 | // ToDo: Avoid unwraps and handle errors with 'Err::NotEnoughData' 49 | reader.read_u8().unwrap() as VertexIdx, 50 | reader.read_u8().unwrap() as VertexIdx, 51 | reader.read_u8().unwrap() as VertexIdx, 52 | ] 53 | }).collect(), 54 | 16 => (0..num_faces).map(|_| { 55 | [ 56 | reader.read_u16().unwrap() as VertexIdx, 57 | reader.read_u16().unwrap() as VertexIdx, 58 | reader.read_u16().unwrap() as VertexIdx, 59 | ] 60 | }).collect(), 61 | 32 => (0..num_faces).map(|_| { 62 | [ 63 | reader.read_u32().unwrap() as VertexIdx, 64 | reader.read_u32().unwrap() as VertexIdx, 65 | reader.read_u32().unwrap() as VertexIdx, 66 | ] 67 | }).collect(), 68 | _ => unreachable!() 69 | } 70 | }; 71 | Ok(faces) 72 | } 73 | } 74 | 75 | 76 | #[cfg(test)] 77 | mod tests { 78 | use super::*; 79 | use crate::core::attribute::AttributeId; 80 | use crate::encode::connectivity::ConnectivityEncoder; 81 | use crate::encode; 82 | use crate::core::shared::{NdVector, Vector}; 83 | use crate::prelude::{Attribute, AttributeType}; 84 | use crate::shared::connectivity::sequential; 85 | 86 | 87 | #[test] 88 | fn test_encode_connectivity() { 89 | let mut encoder = encode::connectivity::sequential::Sequential::new( 90 | encode::connectivity::sequential::Config { 91 | encoder_method: sequential::Method::DirectIndices, 92 | } 93 | ); 94 | let mut writer = Vec::new(); 95 | let mut faces = vec![ 96 | [9,12,13], [8,9,13], [8,9,10], [1,8,10], [1,10,11], [1,2,11], [2,11,12], [2,12,13], 97 | [8,13,14], [7,8,14], [1,7,8], [0,1,7], [0,1,2], [0,2,3], [2,3,13], [3,13,14], 98 | [7,14,15], [6,7,15], [0,6,7], [0,5,6], [0,3,5], [3,4,5], [3,4,14], [4,14,15], 99 | [6,12,15], [6,9,12], [5,6,9], [5,9,10], [4,5,10], [4,10,11], [4,11,15], [11,12,15] 100 | ]; 101 | let points = vec![NdVector::<3,f32>::zero(); 9]; 102 | let mut point_att = Attribute::from(AttributeId::new(0), points, AttributeType::Position, Vec::new()); 103 | let result = encoder.encode_connectivity(&mut faces, &mut [&mut point_att], &mut writer); 104 | assert!(result.is_ok()); 105 | let mut reader = writer.into_iter(); 106 | let mut decoder = Sequential; 107 | let decoded_faces = decoder.decode_connectivity(&mut reader); 108 | assert_eq!(faces, decoded_faces.unwrap()); 109 | } 110 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/entropy/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod rans; 2 | pub(crate) mod symbol_coding; -------------------------------------------------------------------------------- /draco-oxide/src/decode/header/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::ByteReader; 2 | use crate::core::bit_coder::ReaderErr; 3 | use crate::shared::header::EncoderMethod; 4 | 5 | 6 | #[derive(thiserror::Error, Debug)] 7 | pub enum Err { 8 | #[error("Not a Draco file")] 9 | NotADracoFile, 10 | #[error("Not enough data: {0}")] 11 | NotEnoughData(#[from] ReaderErr), 12 | } 13 | 14 | pub(crate) struct Header { 15 | pub version_major: u8, 16 | pub version_minor: u8, 17 | pub encoder_type: u8, 18 | pub encoding_method: EncoderMethod, 19 | pub contains_metadata: bool, 20 | } 21 | 22 | const METADATA_FLAG_MASK: u16 = 32768; 23 | 24 | pub fn decode_header(reader: &mut W) -> Result 25 | where 26 | W: ByteReader, 27 | { 28 | // Read the draco string 29 | if !(0..5).map(|_| reader.read_u8().unwrap() as char ) // ToDo: remove unwrap, handle error properly 30 | .zip("DRACO".chars()) 31 | .all(|(a, b)| a == b) 32 | { 33 | return Err(Err::NotADracoFile) 34 | }; 35 | 36 | // Read the version 37 | let version_major = reader.read_u8()?; 38 | let version_minor = reader.read_u8()?; 39 | 40 | // Readd the encoder type 41 | let encoder_type = reader.read_u8()?; 42 | 43 | // Read the encoding method 44 | let encoding_method = EncoderMethod::read_from(reader)?; 45 | 46 | let flags = reader.read_u16()?; 47 | 48 | let contains_metadata = flags & METADATA_FLAG_MASK != 0; 49 | 50 | Ok ( 51 | Header { 52 | version_major, 53 | version_minor, 54 | encoder_type, 55 | encoding_method, 56 | contains_metadata, 57 | } 58 | ) 59 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/metadata/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::core::bit_coder::ReaderErr; 2 | use crate::prelude::ByteReader; 3 | use crate::utils::bit_coder::leb128_read; 4 | 5 | 6 | #[derive(thiserror::Error, Debug)] 7 | pub enum Err { 8 | #[error("Not enough data to decode metadata.")] 9 | NotEnoughData(#[from] ReaderErr), 10 | } 11 | 12 | pub struct Metadata { 13 | pub metadata: Vec, 14 | pub global_metadata: AttributeMetadata, 15 | } 16 | 17 | #[derive(Debug, Clone)] 18 | pub struct SubMetadata { 19 | pub key: Vec, 20 | pub value: Vec, 21 | } 22 | 23 | impl SubMetadata { 24 | pub fn read_from(reader: &mut W) -> Result 25 | where W: ByteReader, 26 | { 27 | let key_length = reader.read_u8()?; 28 | let mut key = vec![0; key_length as usize]; 29 | for i in 0..key_length { 30 | key[i as usize] = reader.read_u8()?; 31 | } 32 | let value_length = reader.read_u8()?; 33 | let mut value = vec![0; value_length as usize]; 34 | for i in 0..value_length { 35 | value[i as usize] = reader.read_u8()?; 36 | } 37 | Ok(SubMetadata { key, value }) 38 | } 39 | } 40 | 41 | #[derive(Debug, Clone)] 42 | pub struct AttributeMetadata { 43 | pub key: Vec, 44 | pub value: Vec, 45 | pub submetadata: Vec, 46 | } 47 | 48 | impl AttributeMetadata { 49 | pub fn read_from(reader: &mut W) -> Result 50 | where W: ByteReader, 51 | { 52 | let key_length = reader.read_u8()?; 53 | let mut key = vec![0; key_length as usize]; 54 | for _ in 0..key_length { 55 | key.push(reader.read_u8()?); 56 | } 57 | let value_length = reader.read_u8()?; 58 | let mut value = vec![0; value_length as usize]; 59 | for _ in 0..value_length { 60 | value.push(reader.read_u8()?); 61 | } 62 | 63 | // read sub_metadata 64 | let num_submetadata = leb128_read(reader)? as u32; 65 | let mut submetadata = Vec::with_capacity(num_submetadata as usize); 66 | for _ in 0..num_submetadata { 67 | submetadata.push(SubMetadata::read_from(reader)?); 68 | } 69 | Ok(AttributeMetadata { 70 | key, 71 | value, 72 | submetadata: submetadata, 73 | }) 74 | } 75 | 76 | pub fn empty_metadta() -> Self { 77 | AttributeMetadata { 78 | key: Vec::new(), 79 | value: Vec::new(), 80 | submetadata: Vec::new(), 81 | } 82 | } 83 | } 84 | 85 | pub fn decode_metadata(reader: &mut W) -> Result 86 | where W: ByteReader, 87 | { 88 | let num_metadata = reader.read_u32()?; 89 | let mut metadta_id = Vec::with_capacity(num_metadata as usize); 90 | let mut metadata = Vec::new(); 91 | metadata.resize(num_metadata as usize, AttributeMetadata::empty_metadta()); 92 | for _ in 0..num_metadata { 93 | metadta_id.push(leb128_read(reader)?); 94 | metadata[*metadta_id.last().unwrap() as usize] = AttributeMetadata::read_from(reader)?; 95 | } 96 | let global_metadta = AttributeMetadata::read_from(reader)?; 97 | 98 | let out = Metadata { 99 | metadata, 100 | global_metadata: global_metadta, 101 | }; 102 | 103 | Ok(out) 104 | } -------------------------------------------------------------------------------- /draco-oxide/src/decode/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{debug_expect, prelude::{ByteReader, ConfigType}, Mesh}; 2 | 3 | mod header; 4 | mod metadata; 5 | // mod connectivity; 6 | // mod attribute; 7 | mod entropy; 8 | 9 | pub fn decode(reader: &mut W, cfg: Config) -> Result 10 | where W: ByteReader 11 | { 12 | // Decode header 13 | let header = header::decode_header(reader) 14 | .map_err(|r| Err::HeaderError(r))?; 15 | 16 | debug_expect!("Header done, now starting metadata.", reader); 17 | 18 | // Decode metadata 19 | if header.contains_metadata { 20 | let metadata = metadata::decode_metadata(reader) 21 | .map_err(|r| Err::MetadataError(r))?; 22 | } 23 | 24 | debug_expect!("Metadata done, now starting connectivity.", reader); 25 | 26 | // Decode connectivity 27 | // let connectivity = connectivity::decode_connectivity_att(reader, header) 28 | // .map_err(|r| Err::ConnectivityError(r))?; 29 | 30 | debug_expect!("Connectivity done, now starting attributes.", reader); 31 | 32 | // Decode attributes 33 | // let attributes = attribute::decode_attributes(reader, cfg.attribute_decoder_cfg, connectivity) 34 | // .map_err(|r| Err::AttributeError(r))?; 35 | 36 | debug_expect!("All done", reader); 37 | 38 | // // Create mesh 39 | let mut mesh = Mesh::new(); 40 | // for att in attributes { 41 | // mesh.add_attribute(att); 42 | // } 43 | 44 | Ok(mesh) 45 | } 46 | 47 | 48 | #[derive(Debug, Clone)] 49 | pub struct Config { 50 | // attribute_decoder_cfg: attribute::Config, 51 | } 52 | 53 | impl ConfigType for Config { 54 | fn default() -> Self { 55 | Self { 56 | // attribute_decoder_cfg: attribute::Config::default(), 57 | } 58 | } 59 | } 60 | 61 | 62 | #[remain::sorted] 63 | #[derive(thiserror::Error, Debug)] 64 | pub enum Err { 65 | // #[error("Attribute encoding error")] 66 | // AttributeError(#[from] attribute::Err), 67 | // #[error("Connectivity encoding error")] 68 | // ConnectivityError(#[from] connectivity::Err), 69 | #[error("Header encoding error")] 70 | HeaderError(#[from] header::Err), 71 | #[error("Metadata encoding error")] 72 | MetadataError(#[from] metadata::Err), 73 | } 74 | 75 | -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod attribute_encoder; 2 | pub(crate) mod portabilization; 3 | pub(crate) mod prediction_transform; 4 | 5 | use crate::encode::attribute::portabilization::PortabilizationType; 6 | use crate::encode::connectivity::ConnectivityEncoderOutput; 7 | #[cfg(feature = "evaluation")] 8 | use crate::eval; 9 | 10 | use crate::prelude::{Attribute, ByteWriter, ConfigType}; 11 | use crate::shared::connectivity::edgebreaker::TraversalType; 12 | 13 | pub fn encode_attributes( 14 | atts: Vec, 15 | writer: &mut W, 16 | conn_out: ConnectivityEncoderOutput<'_>, 17 | cfg: &super::Config, 18 | ) -> Result<(), Err> 19 | where W: ByteWriter 20 | { 21 | #[cfg(feature = "evaluation")] 22 | eval::scope_begin("attributes", writer); 23 | 24 | // Write the number of attribute encoders/decoders (In draco-oxide, this is the same as the number of attributes as 25 | // each attribute has its own encoder/decoder) 26 | writer.write_u8(atts.len() as u8); 27 | #[cfg(feature = "evaluation")] 28 | eval::write_json_pair("attributes count", atts.len().into(), writer); 29 | 30 | for (i, att) in atts.iter().enumerate() { 31 | if cfg.encoder_method == crate::shared::header::EncoderMethod::Edgebreaker { 32 | // encode decoder id 33 | writer.write_u8((i as u8).wrapping_sub(1)); 34 | // encode attribute type 35 | att.get_domain().write_to(writer); 36 | // write traversal method for attribute encoding/decoding sequencer. We currently only support depth-first traversal. 37 | TraversalType::DepthFirst.write_to(writer); 38 | } 39 | } 40 | 41 | #[cfg(feature = "evaluation")] 42 | eval::array_scope_begin("attributes", writer); 43 | 44 | let mut port_atts: Vec = Vec::new(); 45 | for att in &atts { 46 | // Write 1 to indicate that the encoder is for one attribute. 47 | writer.write_u8(1); 48 | 49 | att.get_attribute_type().write_to(writer); 50 | att.get_component_type().write_to(writer); 51 | writer.write_u8(att.get_num_components() as u8); 52 | writer.write_u8(0); // Normalized flag, currently not used. 53 | writer.write_u8(att.get_id().as_usize() as u8); // unique id 54 | 55 | // write the decoder type. 56 | PortabilizationType::default_for(att.get_attribute_type()).write_to(writer); 57 | } 58 | 59 | for (i, att) in atts.into_iter().enumerate() { 60 | #[cfg(feature = "evaluation")] 61 | eval::scope_begin("attribute", writer); 62 | 63 | let parents_ids = att.get_parents(); 64 | let parents = parents_ids.iter() 65 | .map(|id| port_atts.iter().find(|att| att.get_id() == *id).unwrap()) 66 | .collect::>(); 67 | 68 | let ty = att.get_attribute_type(); 69 | let len = att.len(); 70 | let encoder = attribute_encoder::AttributeEncoder::new( 71 | att, 72 | i, 73 | &parents, 74 | &conn_out, 75 | writer, 76 | attribute_encoder::Config::default_for(ty, len), 77 | ); 78 | 79 | let port_att = encoder.encode::()?; 80 | port_atts.push(port_att); 81 | 82 | #[cfg(feature = "evaluation")] 83 | eval::scope_end(writer); 84 | } 85 | 86 | #[cfg(feature = "evaluation")] 87 | { 88 | eval::array_scope_end(writer); 89 | eval::scope_end(writer); 90 | } 91 | 92 | Ok(()) 93 | } 94 | 95 | 96 | #[derive(Clone, Debug)] 97 | pub struct Config { 98 | #[allow(unused)] // This field is unused in the current implementation, as we only support the default attribute encoder configuration. 99 | cfgs: Vec, 100 | } 101 | 102 | impl ConfigType for Config { 103 | fn default() -> Self { 104 | Self { 105 | cfgs: vec![attribute_encoder::Config::default()], 106 | } 107 | } 108 | } 109 | 110 | #[remain::sorted] 111 | #[derive(thiserror::Error, Debug)] 112 | pub enum Err { 113 | #[error("Attribute encoding error: {0}")] 114 | AttributeError(#[from] attribute_encoder::Err) 115 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/portabilization/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod quantization_coordinate_wise; 2 | pub mod octahedral_quantization; 3 | pub mod to_bits; 4 | 5 | use crate::core::shared::{ConfigType, Vector}; 6 | use crate::debug_write; 7 | use crate::core::bit_coder::ByteWriter; 8 | use crate::prelude::{Attribute, AttributeType, NdVector}; 9 | use crate::shared::attribute::Portable; 10 | 11 | pub enum Portabilization 12 | where Data: Vector + Portable 13 | { 14 | QuantizationCoordinateWise(quantization_coordinate_wise::QuantizationCoordinateWise), 15 | OctahedralQuantization(octahedral_quantization::OctahedralQuantization), 16 | ToBits(to_bits::ToBits), 17 | } 18 | 19 | impl Portabilization 20 | where 21 | Data: Vector + Portable, 22 | NdVector: Vector, 23 | NdVector: Vector + Portable, 24 | { 25 | /// creates a new instance of the portabilization, computes the metadata, and 26 | /// writes the metadata to the stream. 27 | // enum_dispatch does not support associated functions, we explicitly write the 28 | // constructor. 29 | pub fn new(att: Attribute, cfg: Config, writer: &mut W) -> Self 30 | where W: ByteWriter 31 | { 32 | debug_write!("Start of Portabilization Metadata", writer); 33 | // cfg.type_.write_to(writer); 34 | let out = match cfg.type_ { 35 | PortabilizationType::QuantizationCoordinateWise => { 36 | Portabilization::QuantizationCoordinateWise ( 37 | quantization_coordinate_wise::QuantizationCoordinateWise::<_,N>::new(att, cfg, writer) 38 | ) 39 | }, 40 | PortabilizationType::OctahedralQuantization => { 41 | Portabilization::OctahedralQuantization( 42 | octahedral_quantization::OctahedralQuantization::new(att, cfg, writer) 43 | ) 44 | }, 45 | PortabilizationType::ToBits => { 46 | Portabilization::ToBits( 47 | to_bits::ToBits::new(att, cfg, writer) 48 | ) 49 | }, 50 | PortabilizationType::Integer => { 51 | unimplemented!("Integer portabilization is not implemented yet.") 52 | }, 53 | }; 54 | debug_write!("End of Portabilization Metadata", writer); 55 | out 56 | } 57 | 58 | pub fn portabilize(self) -> Attribute { 59 | match self { 60 | Portabilization::QuantizationCoordinateWise(qcw) => qcw.portabilize(), 61 | Portabilization::OctahedralQuantization(oct) => oct.portabilize(), 62 | Portabilization::ToBits(tb) => tb.portabilize(), 63 | } 64 | } 65 | } 66 | 67 | pub trait PortabilizationImpl 68 | where 69 | NdVector: Vector, 70 | { 71 | /// portabilizes the whole data. 72 | fn portabilize(self) -> Attribute; 73 | } 74 | 75 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 76 | pub enum PortabilizationType { 77 | QuantizationCoordinateWise, 78 | OctahedralQuantization, 79 | #[allow(dead_code)] 80 | Integer, 81 | ToBits, 82 | } 83 | 84 | impl PortabilizationType { 85 | pub(crate) fn get_id(&self) -> u8 { 86 | match self { 87 | PortabilizationType::ToBits => 1, 88 | PortabilizationType::Integer => 1, // Integer is not used in the current implementation, but kept for compatibility. 89 | PortabilizationType::QuantizationCoordinateWise => 2, 90 | PortabilizationType::OctahedralQuantization => 3, 91 | } 92 | } 93 | 94 | pub(crate) fn write_to(&self, writer: &mut W) 95 | where W: ByteWriter 96 | { 97 | let id = self.get_id(); 98 | writer.write_u8(id); 99 | } 100 | 101 | pub(crate) fn default_for(ty: AttributeType) -> Self { 102 | match ty { 103 | AttributeType::Normal => PortabilizationType::OctahedralQuantization, 104 | AttributeType::Custom => PortabilizationType::ToBits, 105 | _ => PortabilizationType::QuantizationCoordinateWise, // default 106 | } 107 | } 108 | } 109 | 110 | #[derive(Clone, Copy, Debug)] 111 | pub struct Config { 112 | pub type_: PortabilizationType, 113 | pub quantization_bits: u8, 114 | } 115 | 116 | impl ConfigType for Config { 117 | fn default()-> Self { 118 | Config { 119 | type_: PortabilizationType::QuantizationCoordinateWise, 120 | quantization_bits: 11, 121 | } 122 | } 123 | } 124 | 125 | impl Config { 126 | pub fn default_for(ty: AttributeType) -> Self { 127 | match ty { 128 | AttributeType::Normal => Config { 129 | type_: PortabilizationType::OctahedralQuantization, 130 | quantization_bits: 8, 131 | }, 132 | AttributeType::TextureCoordinate => Config { 133 | type_: PortabilizationType::QuantizationCoordinateWise, 134 | quantization_bits: 10, 135 | }, 136 | AttributeType::Custom => Config { 137 | type_: PortabilizationType::ToBits, 138 | quantization_bits: 11, // default quantization bits (not used for ToBits) 139 | }, 140 | _ => Self::default(), 141 | } 142 | } 143 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/portabilization/octahedral_quantization.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::AttributeValueIdx; 2 | use crate::core::shared::Vector; 3 | use crate::encode::attribute::prediction_transform::geom::into_faithful_oct_quantization; 4 | use crate::encode::attribute::prediction_transform::geom::octahedral_transform; 5 | use crate::prelude::Attribute; 6 | use crate::prelude::AttributeType; 7 | use crate::prelude::ByteWriter; 8 | use crate::prelude::NdVector; 9 | use crate::shared::attribute::Portable; 10 | 11 | use super::Config; 12 | use super::PortabilizationImpl; 13 | 14 | pub struct OctahedralQuantization 15 | { 16 | /// iterator over the attribute values. 17 | /// this is not 'Vec<_>' because we want to nicely consume the data. 18 | att: Attribute, 19 | 20 | /// the size of the quantization 21 | quantization_bits: u8, 22 | 23 | _marker: std::marker::PhantomData, 24 | } 25 | 26 | impl OctahedralQuantization 27 | where 28 | Data: Vector, 29 | NdVector: Vector, 30 | { 31 | pub fn new(att: Attribute, cfg: Config, writer: &mut W) -> Self 32 | where W: ByteWriter 33 | { 34 | assert!( 35 | att.get_attribute_type()==AttributeType::Normal, 36 | "Octahedral quantization can only be applied to normal attributes." 37 | ); 38 | 39 | // encode the quantization bits. 40 | writer.write_u8(cfg.quantization_bits); 41 | 42 | Self { 43 | att, 44 | quantization_bits: cfg.quantization_bits, 45 | _marker: std::marker::PhantomData, 46 | } 47 | } 48 | 49 | fn portabilize_value(&mut self, val: Data) -> NdVector<2, i32> { 50 | let val_oct = octahedral_transform(val) + NdVector::<2, f32>::from([1.0,1.0]); 51 | debug_assert!( 52 | *val_oct.get(0) >= 0.0 && *val_oct.get(0) <= 2.0 && 53 | *val_oct.get(1) >= 0.0 && *val_oct.get(1) <= 2.0, 54 | "Octahedral transformed value out of bounds: {:?}", 55 | val_oct 56 | ); 57 | let quantized = val_oct * ((1<::zero(); 59 | for i in 0..2 { 60 | *out.get_mut(i) = *quantized.get(i) as i32; 61 | } 62 | let out = into_faithful_oct_quantization(out); 63 | out 64 | } 65 | } 66 | 67 | impl PortabilizationImpl for OctahedralQuantization 68 | where 69 | Data: Vector + Portable, 70 | NdVector: Vector, 71 | { 72 | fn portabilize(mut self) -> Attribute { 73 | let mut out = Vec::new(); 74 | for i in 0..self.att.num_unique_values() { 75 | let i = AttributeValueIdx::from(i); 76 | out.push(self.portabilize_value( 77 | self.att.get_unique_val::(i) 78 | )); 79 | } 80 | let mut port_att = Attribute::from_without_removing_duplicates( 81 | self.att.get_id(), 82 | out, 83 | self.att.get_attribute_type(), 84 | self.att.get_domain(), 85 | self.att.get_parents().clone() 86 | ); 87 | port_att.set_point_to_att_val_map(self.att.take_point_to_att_val_map()); 88 | port_att 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/portabilization/quantization_coordinate_wise.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{AttributeValueIdx, DataValue, Vector}; 2 | use crate::prelude::{Attribute, ByteWriter, NdVector}; 3 | use crate::shared::attribute::Portable; 4 | 5 | use super::{Config, PortabilizationImpl}; 6 | 7 | pub(crate) struct QuantizationCoordinateWise 8 | where Data: Vector 9 | { 10 | att: Attribute, 11 | range_size: f32, 12 | min_values: NdVector, 13 | quantization_bits: u8, 14 | _phantom: std::marker::PhantomData, 15 | } 16 | 17 | impl QuantizationCoordinateWise 18 | where 19 | NdVector: Vector, 20 | NdVector: Vector + Portable, 21 | Data: Vector + Portable, 22 | Data::Component: DataValue 23 | { 24 | pub fn new(att: Attribute, cfg: Config, writer: &mut W) -> Self 25 | where 26 | W: ByteWriter, 27 | { 28 | let mut min_values = NdVector::::zero(); 29 | for val in att.unique_vals_as_slice::() { 30 | for i in 0..N { 31 | let component = val.get(i).to_f64() as f32; 32 | if component < *min_values.get(i) { 33 | *min_values.get_mut(i) = component; 34 | } 35 | } 36 | } 37 | 38 | let mut max_values = NdVector::::zero(); 39 | for val in att.unique_vals_as_slice::() { 40 | for i in 0..N { 41 | let component = val.get(i).to_f64() as f32; 42 | if component > *max_values.get(i) { 43 | *max_values.get_mut(i) = component; 44 | } 45 | } 46 | } 47 | 48 | let mut delta_max = 0.0; 49 | for i in 0..N { 50 | let delta = *max_values.get(i) - *min_values.get(i); 51 | if delta > delta_max { 52 | delta_max = delta; 53 | } 54 | } 55 | 56 | // write metadata 57 | min_values.write_to(writer); 58 | delta_max.write_to(writer); 59 | writer.write_u8(cfg.quantization_bits); 60 | 61 | Self { 62 | att, 63 | range_size: delta_max, 64 | min_values, 65 | quantization_bits: cfg.quantization_bits, 66 | _phantom: std::marker::PhantomData, 67 | } 68 | } 69 | 70 | fn portabilize_value(&mut self, val: Data) -> NdVector { 71 | // convert value to float vector TODO: implement the vector conversion so that this will be one line 72 | let val: NdVector = { 73 | let mut out = NdVector::::zero(); 74 | for i in 0..N { 75 | *out.get_mut(i) = val.get(i).to_f64() as f32; 76 | } 77 | out 78 | }; 79 | let diff = val - self.min_values; 80 | let normalized = if self.range_size==0.0 { 81 | diff 82 | } else { 83 | diff / self.range_size 84 | }; 85 | let quantized = normalized * f32::from_u64((1<::zero(); 87 | for i in 0..N { 88 | *out.get_mut(i) = (*quantized.get(i) + 0.5).to_i64() as i32; 89 | } 90 | out 91 | } 92 | } 93 | 94 | impl PortabilizationImpl for QuantizationCoordinateWise 95 | where 96 | NdVector: Vector, 97 | NdVector: Vector + Portable, 98 | Data: Vector + Portable, 99 | { 100 | fn portabilize(mut self) -> Attribute { 101 | let mut out = Vec::new(); 102 | for i in 0..self.att.num_unique_values() { 103 | let i = AttributeValueIdx::from(i); 104 | out.push(self.portabilize_value( 105 | self.att.get_unique_val::(i) 106 | )); 107 | } 108 | let mut port_att = Attribute::from_without_removing_duplicates( 109 | self.att.get_id(), 110 | out, 111 | self.att.get_attribute_type(), 112 | self.att.get_domain(), 113 | self.att.get_parents().clone() 114 | ); 115 | port_att.set_point_to_att_val_map(self.att.take_point_to_att_val_map()); 116 | port_att 117 | } 118 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/portabilization/to_bits.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::DataValue; 2 | use crate::core::shared::Vector; 3 | use crate::prelude::Attribute; 4 | use crate::prelude::ByteWriter; 5 | use crate::prelude::NdVector; 6 | use crate::shared::attribute::Portable; 7 | 8 | #[cfg(feature = "evaluation")] 9 | use crate::eval; 10 | 11 | use super::Config; 12 | use super::PortabilizationImpl; 13 | 14 | pub struct ToBits 15 | where Data: Vector 16 | { 17 | /// iterator over the attribute values. 18 | /// this is not 'Vec<_>' because we want nicely consume the data. 19 | att: Attribute, 20 | 21 | _marker: std::marker::PhantomData, 22 | } 23 | 24 | impl ToBits 25 | where 26 | Data: Vector + Portable, 27 | Data::Component: DataValue 28 | { 29 | pub fn new(att: Attribute, _cfg: Config, _writer: &mut W) -> Self 30 | where W: ByteWriter 31 | { 32 | #[cfg(feature = "evaluation")] 33 | eval::write_json_pair("portabilization", "ToBits".into(), _writer); 34 | Self { 35 | att, 36 | _marker: std::marker::PhantomData, 37 | } 38 | } 39 | } 40 | 41 | impl PortabilizationImpl for ToBits 42 | where 43 | Data: Vector + Portable, 44 | NdVector: Vector, 45 | 46 | { 47 | fn portabilize(self) -> Attribute { 48 | self.att 49 | } 50 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/prediction_transform/difference.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::Vector; 2 | use crate::prelude::{ByteWriter, NdVector}; 3 | use crate::utils::to_positive_i32_vec; 4 | use super::PredictionTransformImpl; 5 | 6 | #[cfg(feature = "evaluation")] 7 | use crate::eval; 8 | 9 | pub struct Difference 10 | { 11 | out: Vec>, 12 | } 13 | 14 | impl Difference 15 | { 16 | pub fn new(_cfg: super::Config) -> Self { 17 | Self { 18 | out: Vec::new(), 19 | } 20 | } 21 | } 22 | 23 | impl PredictionTransformImpl for Difference 24 | { 25 | 26 | fn map_with_tentative_metadata(&mut self, orig: NdVector, pred: NdVector) 27 | where 28 | NdVector: Vector, 29 | { 30 | let corr = orig - pred; 31 | let corr = to_positive_i32_vec(corr); 32 | 33 | self.out.push(corr); 34 | } 35 | 36 | fn squeeze(self, _writer: &mut W) -> Vec> 37 | where W: ByteWriter 38 | { 39 | #[cfg(feature = "evaluation")] 40 | { 41 | eval::write_json_pair("transform type", "Difference".into(), _writer); 42 | eval::array_scope_begin("transformed data", _writer); 43 | for &x in self.out.iter() { 44 | eval::write_arr_elem(x.into(), _writer); 45 | } 46 | eval::array_scope_end(_writer); 47 | } 48 | self.out 49 | } 50 | } 51 | 52 | -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/prediction_transform/oct_orthogonal.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{NdVector, Vector}; 2 | use crate::prelude::ByteWriter; 3 | use super::{ 4 | PredictionTransformImpl 5 | }; 6 | 7 | pub struct OctahedronOrthogonalTransform 8 | { 9 | out: Vec>, 10 | } 11 | 12 | impl OctahedronOrthogonalTransform 13 | { 14 | pub fn new(_cfg: super::Config) -> Self { 15 | Self { 16 | out: Vec::new(), 17 | } 18 | } 19 | } 20 | 21 | impl PredictionTransformImpl for OctahedronOrthogonalTransform 22 | { 23 | fn map_with_tentative_metadata(&mut self, mut orig: NdVector, mut pred: NdVector) 24 | where 25 | NdVector: Vector, 26 | { 27 | // Safety: 28 | // We made sure that the data is two dimensional. 29 | assert!( 30 | N==2, 31 | ); 32 | 33 | // make sure that pred is in the upper hemisphere. 34 | let one = 255/2; 35 | *pred.get_mut(0) -= one; 36 | *pred.get_mut(1) -= one; 37 | *orig.get_mut(0) -= one; 38 | *orig.get_mut(1) -= one; 39 | if pred.get(0).abs() + pred.get(1).abs() > one { 40 | // we need to flip the z-axis. 41 | // In the octahedron representation, this means that we need to flip inside out. 42 | let pred0 = *pred.get(0); 43 | let quadrant_sign = -(pred.get(0) * pred.get(1)).signum(); 44 | *pred.get_mut(0) = quadrant_sign*pred.get(1) + pred.get(0).signum() * one; 45 | *pred.get_mut(1) = quadrant_sign*pred0 + pred.get(1).signum() * one; 46 | let orig0 = *orig.get(0); 47 | let quadrant_sign = -(orig.get(0) * orig.get(1)).signum(); 48 | *orig.get_mut(0) = quadrant_sign*orig.get(1) + orig.get(0).signum() * one; 49 | *orig.get_mut(1) = quadrant_sign*orig0 + orig.get(1).signum() * one; 50 | } 51 | 52 | // Now rotate the sphere around the z-axis so that the x and y coordinates of pred are both negative. 53 | if pred != NdVector::::zero() { 54 | while *pred.get(0) >= 0 || *pred.get(1) > 0 { 55 | // rotate 90 degrees clockwise 56 | let tmp = *pred.get(0); 57 | *pred.get_mut(0) = -pred.get(1); 58 | *pred.get_mut(1) = tmp; 59 | 60 | let tmp = *orig.get(0); 61 | *orig.get_mut(0) = -orig.get(1); 62 | *orig.get_mut(1) = tmp; 63 | } 64 | } 65 | 66 | // Now we take the difference and make it positive. 67 | let mut corr = orig - pred; 68 | for i in 0..N { 69 | if *corr.get(i) < 0 { 70 | *corr.get_mut(i) += 255; 71 | } 72 | } 73 | self.out.push(corr); 74 | } 75 | 76 | fn squeeze(self, writer: &mut W) -> Vec> 77 | where W: ByteWriter 78 | { 79 | // write the max quantized value. 80 | writer.write_u32(255); 81 | // write center of the octahedron. 82 | writer.write_u32(255/2); 83 | 84 | self.out 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/prediction_transform/oct_reflection.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{NdVector, Vector}; 2 | use crate::prelude::ByteWriter; 3 | 4 | use super::PredictionTransformImpl; 5 | 6 | 7 | pub struct OctahedronReflectionTransform 8 | { 9 | out: Vec>, 10 | } 11 | 12 | impl OctahedronReflectionTransform { 13 | pub fn new(_cfg: super::Config) -> Self { 14 | Self { 15 | out: Vec::new(), 16 | } 17 | } 18 | } 19 | 20 | impl PredictionTransformImpl for OctahedronReflectionTransform 21 | { 22 | fn map_with_tentative_metadata(&mut self, mut orig: NdVector, mut pred: NdVector) 23 | where NdVector: Vector, 24 | { 25 | // Safety: 26 | // We made sure that the data is three dimensional. 27 | debug_assert!( 28 | N==2, 29 | ); 30 | 31 | unsafe { 32 | if *pred.get_unchecked(2) < 0 { 33 | *pred.get_unchecked_mut(2) *= -1; 34 | *orig.get_unchecked_mut(2) *= -1; 35 | } 36 | }; 37 | 38 | self.out.push( orig - pred ); 39 | } 40 | 41 | fn squeeze(self, _writer: &mut W) -> Vec> 42 | where W: ByteWriter 43 | { 44 | unimplemented!() 45 | } 46 | 47 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/prediction_transform/orthogonal.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{NdVector, Vector}; 2 | use crate::prelude::ByteWriter; 3 | 4 | use super::PredictionTransformImpl; 5 | 6 | pub struct OrthogonalTransform 7 | { 8 | #[allow(unused)] 9 | out: Vec>, 10 | 11 | /// This metadata records whether the prediction uses 12 | /// (1,0,0) or (0,1,0) as the reference vector. 13 | #[allow(unused)] 14 | metadata: Vec, 15 | } 16 | 17 | impl OrthogonalTransform 18 | { 19 | pub fn new(_cfg: super::Config) -> Self { 20 | Self { 21 | out: Vec::new(), 22 | metadata: Vec::new(), 23 | } 24 | } 25 | } 26 | 27 | impl PredictionTransformImpl for OrthogonalTransform { 28 | // ToDo: Add dynamic data check. 29 | 30 | fn map_with_tentative_metadata(&mut self, _orig: NdVector, _pred: NdVector) 31 | where NdVector: Vector, 32 | { 33 | unimplemented!(); 34 | // let one = Data::Component::one(); 35 | // let zero = Data::Component::zero(); 36 | 37 | // // project 'r' to the plane defined by 'pred' 38 | // let pred_norm_squared = pred.dot(pred); 39 | // let ref_on_pred_perp = if unsafe{ pred.get_unchecked(1).abs() } > one/Data::Component::from_u64(10) { 40 | // self.metadata.push(true); 41 | // // Safety: 42 | // // dereferencing the constant-sized array by a constant index 43 | // unsafe { 44 | // let mut out = pred * (*pred.get_unchecked(0) / pred_norm_squared); 45 | // *out.get_unchecked_mut(0) += one; 46 | // out 47 | // } 48 | // } else { 49 | // self.metadata.push(false); 50 | // // Safety: 51 | // // dereferencing the constant-sized array by a constant index 52 | // unsafe { 53 | // let mut out = pred * (*pred.get_unchecked(1) / pred_norm_squared); 54 | // *out.get_unchecked_mut(1) += one; 55 | // out 56 | // } 57 | // }; 58 | 59 | // let pred_norm_squared = pred_norm_squared.to_f64(); 60 | 61 | // let pred_cross_orig = pred.cross(orig); 62 | // // 'ref_on_pred_perp' and pred_'cross_orig' are on the same plane defined by 'pred' 63 | // debug_assert!(pred_cross_orig.dot(pred).abs() < one/Data::Component::from_u64(1_000_000)); 64 | // debug_assert!(ref_on_pred_perp.dot(pred).abs() < one/Data::Component::from_u64(1_000_000)); 65 | 66 | // // get the angle between 'ref_on_pred_perp' and 'pred_cross_orig' 67 | // let ref_on_pred_perp_norm_squared = ref_on_pred_perp.dot(ref_on_pred_perp).to_f64(); 68 | // let difference = ref_on_pred_perp-pred_cross_orig; 69 | // let difference_norm_squared = difference.dot(difference).to_f64(); 70 | // let sign = if pred.dot(ref_on_pred_perp.cross(pred_cross_orig)) > zero { 1_f64 } else { -1_f64 }; 71 | // let first_angle = sign * (1_f64+ref_on_pred_perp_norm_squared-difference_norm_squared/2_f64/ref_on_pred_perp_norm_squared.sqrt()).acos(); 72 | 73 | 74 | // // get the angle between 'pred' and 'orig' 75 | // let orig_norm_squared = orig.dot(orig).to_f64(); 76 | // let difference = pred - orig; 77 | // let difference_norm_squared = difference.dot(difference).to_f64(); 78 | // let second_angle = (pred_norm_squared+orig_norm_squared-difference_norm_squared/(2_f64*pred_norm_squared.sqrt()*orig_norm_squared.sqrt())).acos(); 79 | 80 | // self.out.push(NdVector::from( 81 | // [first_angle, second_angle] 82 | // )); 83 | 84 | } 85 | 86 | fn squeeze(self, _writer: &mut W) -> Vec> 87 | where W: ByteWriter 88 | { 89 | unimplemented!() 90 | } 91 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/attribute/prediction_transform/wrapped_difference.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::Vector; 2 | use crate::prelude::{ByteWriter, NdVector}; 3 | use crate::shared::attribute::Portable; 4 | use crate::utils::to_positive_i32_vec; 5 | use super::PredictionTransformImpl; 6 | 7 | #[cfg(feature = "evaluation")] 8 | use crate::eval; 9 | 10 | pub struct WrappedDifference 11 | { 12 | _cfg: super::Config, 13 | preds: Vec>, 14 | origs: Vec>, 15 | max: i32, 16 | min: i32, 17 | } 18 | 19 | impl WrappedDifference 20 | { 21 | pub fn new(_cfg: super::Config) -> Self { 22 | Self { 23 | _cfg, 24 | preds: Vec::new(), 25 | origs: Vec::new(), 26 | max: i32::MIN, 27 | min: i32::MAX, 28 | } 29 | } 30 | } 31 | 32 | impl PredictionTransformImpl for WrappedDifference 33 | where NdVector: Vector 34 | { 35 | 36 | fn map_with_tentative_metadata(&mut self, orig: NdVector, pred: NdVector) 37 | where 38 | NdVector: Vector, 39 | { 40 | // Update min and max values for the wrapped difference 41 | for i in 0..N { 42 | let orig_val = *orig.get(i); 43 | if orig_val > self.max { 44 | self.max = orig_val; 45 | } 46 | if orig_val < self.min { 47 | self.min = orig_val; 48 | } 49 | } 50 | self.origs.push(orig); 51 | self.preds.push(pred); 52 | } 53 | 54 | fn squeeze(self, writer: &mut W) -> Vec> 55 | where W: ByteWriter 56 | { 57 | #[cfg(feature = "evaluation")] 58 | { 59 | eval::write_json_pair("transform type", "WrappedDifference".into(), writer); 60 | eval::array_scope_begin("prediction data", writer); 61 | for &x in self.preds.iter() { 62 | eval::write_arr_elem(x.into(), writer); 63 | } 64 | eval::array_scope_end(writer); 65 | } 66 | let diff = self.max - self.min; 67 | let max_diff = 1 + diff; 68 | let mut max_corr = max_diff / 2; 69 | let min_corr = -max_corr; 70 | if (max_diff & 1) == 0 { 71 | max_corr -= 1; 72 | } 73 | 74 | // compute the wrapped difference 75 | let mut out = Vec::with_capacity(self.origs.len()); 76 | for (orig, mut pred) in self.origs.into_iter().zip(self.preds.into_iter()) { 77 | let mut corr = NdVector::zero(); 78 | for i in 0..N { 79 | // clamp the prediction values 80 | *pred.get_mut(i) = *pred.get(i).clamp(&self.min, &self.max); 81 | // then compute the wrapped difference 82 | let val = *orig.get(i) - *pred.get(i); 83 | if val > max_corr { 84 | *corr.get_mut(i) = val - max_diff; 85 | } else if val < min_corr { 86 | *corr.get_mut(i) = val + max_diff; 87 | } else { 88 | *corr.get_mut(i) = val; 89 | } 90 | } 91 | out.push(to_positive_i32_vec(corr)); 92 | } 93 | 94 | // write metadata 95 | self.min.write_to(writer); 96 | self.max.write_to(writer); 97 | 98 | out 99 | } 100 | } 101 | 102 | -------------------------------------------------------------------------------- /draco-oxide/src/encode/connectivity/config.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/src/encode/connectivity/config.rs -------------------------------------------------------------------------------- /draco-oxide/src/encode/connectivity/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod config; 2 | pub(crate) mod edgebreaker; 3 | pub(crate) mod sequential; 4 | 5 | use std::fmt::Debug; 6 | 7 | use crate::core::bit_coder::ByteWriter; 8 | use crate::core::shared::{ConfigType, PointIdx}; 9 | use crate::encode::connectivity::edgebreaker::{DefaultTraversal, ValenceTraversal}; 10 | use crate::prelude::{Attribute, AttributeType}; 11 | use crate::shared::connectivity::edgebreaker::EdgebreakerKind; 12 | 13 | #[cfg(feature = "evaluation")] 14 | use crate::eval; 15 | 16 | /// entry point for encoding connectivity. 17 | pub fn encode_connectivity<'faces, W>( 18 | faces: &'faces[[PointIdx; 3]], 19 | atts: &mut [Attribute], 20 | writer: &mut W, 21 | #[allow(unused)] // This parameter is unused in the current implementation, as we only support default configuration. 22 | cfg: &super::Config, 23 | ) -> Result, Err> 24 | where W: ByteWriter 25 | { 26 | #[cfg(feature = "evaluation")] 27 | eval::scope_begin("connectivity info", writer); 28 | 29 | let result = encode_connectivity_datatype_unpacked(faces, atts, writer, Config::default()); 30 | 31 | #[cfg(feature = "evaluation")] 32 | eval::scope_end(writer); 33 | result 34 | } 35 | 36 | pub fn encode_connectivity_datatype_unpacked<'faces, W>( 37 | faces: &'faces[[PointIdx; 3]], 38 | atts: &mut [Attribute], 39 | writer: &mut W, 40 | cfg: Config, 41 | ) -> Result, Err> 42 | where W: ByteWriter, 43 | { 44 | let result = match cfg { 45 | Config::Edgebreaker(cfg) => { 46 | #[cfg(feature = "evaluation")] 47 | eval::scope_begin("edgebreaker", writer); 48 | 49 | let result = match cfg.traversal { 50 | EdgebreakerKind::Standard => { 51 | let encoder = edgebreaker::Edgebreaker::::new(cfg, atts, faces)?; 52 | encoder.encode_connectivity(&faces, writer) 53 | }, 54 | EdgebreakerKind::Predictive => { 55 | unimplemented!("Predictive edgebreaker encoding is not implemented yet"); 56 | }, 57 | EdgebreakerKind::Valence => { 58 | let encoder = edgebreaker::Edgebreaker::::new(cfg, atts, faces)?; 59 | encoder.encode_connectivity(&faces, writer) 60 | }, 61 | }; 62 | 63 | #[cfg(feature = "evaluation")] 64 | eval::scope_end(writer); 65 | 66 | result.map(|o| ConnectivityEncoderOutput::Edgebreaker(o))? 67 | }, 68 | Config::Sequential(cfg) => { 69 | #[cfg(feature = "evaluation")] 70 | eval::scope_begin("sequential", writer); 71 | 72 | let num_points = atts.iter() 73 | .find(|att| att.get_attribute_type() == AttributeType::Position) 74 | .unwrap() 75 | .len(); 76 | let encoder = sequential::Sequential::new(cfg, num_points); 77 | let result = encoder.encode_connectivity(faces, writer)?; 78 | 79 | #[cfg(feature = "evaluation")] 80 | eval::scope_end(writer); 81 | 82 | ConnectivityEncoderOutput::Sequential(result) 83 | } 84 | }; 85 | Ok(result) 86 | } 87 | 88 | pub trait ConnectivityEncoder { 89 | type Err; 90 | type Config; 91 | type Output; 92 | fn encode_connectivity( 93 | self, 94 | faces: &[[PointIdx; 3]], 95 | buffer: &mut W 96 | ) -> Result 97 | where W: ByteWriter; 98 | } 99 | 100 | pub(crate) enum ConnectivityEncoderOutput<'faces> { 101 | Edgebreaker(edgebreaker::Output<'faces>), 102 | Sequential(()), 103 | } 104 | 105 | #[remain::sorted] 106 | #[derive(thiserror::Error, Debug)] 107 | pub enum Err { 108 | #[error("Edgebreaker encoding error: {0}")] 109 | EdgebreakerError(#[from] edgebreaker::Err), 110 | #[error("Position attribute must be of type f32 or f64")] 111 | PositionAttributeTypeError, 112 | #[error("Sequential encoding error: {0}")] 113 | SequentialError(#[from] sequential::Err), 114 | #[error("Too many connectivity attributes")] 115 | TooManyConnectivityAttributes, 116 | } 117 | 118 | #[remain::sorted] 119 | #[derive(Clone, Debug)] 120 | pub enum Config { 121 | Edgebreaker(edgebreaker::Config), 122 | #[allow(unused)] // we currently support only edgebreaker 123 | Sequential(sequential::Config), 124 | } 125 | 126 | impl ConfigType for Config { 127 | fn default()-> Self { 128 | Self::Edgebreaker(edgebreaker::Config::default()) 129 | } 130 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/connectivity/sequential.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::PointIdx; 2 | use crate::core::shared::ConfigType; 3 | use crate::debug_write; 4 | use crate::encode::connectivity::ConnectivityEncoder; 5 | use crate::prelude::ByteWriter; 6 | use crate::shared::connectivity::sequential::index_size_from_vertex_count; 7 | use crate::shared::connectivity::sequential::Method; 8 | use crate::utils::bit_coder::leb128_write; 9 | 10 | pub(crate) struct Sequential { 11 | cfg: Config, 12 | num_points: usize, 13 | } 14 | 15 | impl Sequential { 16 | pub fn new(config: Config, num_points: usize) -> Self { 17 | Self { 18 | cfg: config, 19 | num_points 20 | } 21 | } 22 | 23 | fn encode_direct_indices( 24 | &self, 25 | faces: &[[PointIdx; 3]], 26 | writer: &mut W 27 | ) -> Result<(), Err> 28 | where W: ByteWriter, 29 | { 30 | let index_size = match index_size_from_vertex_count(self.num_points) { 31 | Ok(index_size) => index_size as u8, 32 | Err(err) => return Err(Err::SharedError(err)), 33 | }; 34 | debug_write!("Start of indices", writer); 35 | 36 | if index_size == 21 { 37 | // varint encoding 38 | for face in faces { 39 | leb128_write(usize::from(face[0]) as u64, writer); 40 | leb128_write(usize::from(face[1]) as u64, writer); 41 | leb128_write(usize::from(face[2]) as u64, writer); 42 | } 43 | } else { 44 | // non-varint encoding 45 | match index_size { 46 | 8 => for face in faces { 47 | writer.write_u8(usize::from(face[0]) as u8); 48 | writer.write_u8(usize::from(face[1]) as u8); 49 | writer.write_u8(usize::from(face[2]) as u8); 50 | } 51 | 16 => for face in faces { 52 | writer.write_u16(usize::from(face[0]) as u16); 53 | writer.write_u16(usize::from(face[1]) as u16); 54 | writer.write_u16(usize::from(face[2]) as u16); 55 | }, 56 | 32 => for face in faces { 57 | writer.write_u32(usize::from(face[0]) as u32); 58 | writer.write_u32(usize::from(face[1]) as u32); 59 | writer.write_u32(usize::from(face[2]) as u32); 60 | }, 61 | _ => unreachable!() 62 | } 63 | } 64 | Ok(()) 65 | } 66 | } 67 | 68 | impl ConnectivityEncoder for Sequential { 69 | type Err = Err; 70 | type Config = Config; 71 | type Output = (); 72 | 73 | fn encode_connectivity( 74 | self, 75 | faces: &[[PointIdx; 3]], 76 | writer: &mut W 77 | ) -> Result<(), Err> 78 | where W: ByteWriter, 79 | { 80 | writer.write_u64(faces.len() as u64); 81 | let encoder_method_id = self.cfg.encoder_method.get_id(); 82 | writer.write_u8(encoder_method_id); 83 | self.encode_direct_indices(faces, writer)?; 84 | 85 | Ok(()) 86 | } 87 | } 88 | 89 | #[derive(Clone, Debug)] 90 | pub struct Config { 91 | pub encoder_method: Method, 92 | } 93 | 94 | impl ConfigType for Config { 95 | fn default() -> Self { 96 | Config { 97 | encoder_method: Method::DirectIndices, 98 | } 99 | } 100 | } 101 | 102 | #[remain::sorted] 103 | #[derive(thiserror::Error, Debug)] 104 | pub enum Err { 105 | #[error("Invalid vertex count")] 106 | SharedError(crate::shared::connectivity::sequential::Err), 107 | } 108 | 109 | -------------------------------------------------------------------------------- /draco-oxide/src/encode/entropy/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rans; 2 | pub mod symbol_coding; -------------------------------------------------------------------------------- /draco-oxide/src/encode/header/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::bit_coder::ByteWriter, shared::header::EncoderMethod}; 2 | 3 | #[remain::sorted] 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum Err { 6 | } 7 | 8 | #[derive(Clone, Debug)] 9 | pub enum EncodedGeometryType { 10 | #[allow(unused)] 11 | PointCloud, 12 | TrianglarMesh, 13 | } 14 | 15 | impl EncodedGeometryType { 16 | pub fn get_id(&self) -> u8 { 17 | match self { 18 | EncodedGeometryType::PointCloud => 0, 19 | EncodedGeometryType::TrianglarMesh => 1, 20 | } 21 | } 22 | } 23 | 24 | const METADATA_FLAG_MASK: u16 = 32768; 25 | 26 | pub fn encode_header(writer: &mut W, cfg: &super::Config) -> Result<(), Err> 27 | where 28 | W: ByteWriter, 29 | { 30 | // Write the draco string 31 | "DRACO".as_bytes().iter().for_each(|&b| { 32 | writer.write_u8(b); 33 | }); 34 | 35 | // Write the version 36 | writer.write_u8(2); 37 | writer.write_u8(2); 38 | 39 | // Write encoder type 40 | let id = cfg.geometry_type.get_id(); 41 | writer.write_u8(id); 42 | 43 | // Write the encoding method 44 | // Currently, we only support the edgebreaker method 45 | EncoderMethod::Edgebreaker.write_to(writer); 46 | 47 | // Write the connectivity encoder config 48 | if cfg.metdata { 49 | writer.write_u16(METADATA_FLAG_MASK); 50 | } else { 51 | writer.write_u16(0); 52 | } 53 | 54 | Ok(()) 55 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/metadata/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::core::bit_coder::ByteWriter; 2 | 3 | #[remain::sorted] 4 | #[derive(thiserror::Error, Debug)] 5 | pub enum Err { 6 | 7 | } 8 | 9 | pub fn encode_metadata( 10 | _mesh: &crate::core::mesh::Mesh, 11 | writer: &mut W, 12 | ) -> Result<(), Err> 13 | where W: ByteWriter, 14 | { 15 | // Write Encoder 16 | writer.write_u32(0); 17 | 18 | // ToDo: Implement metadata encoding 19 | Ok(()) 20 | } -------------------------------------------------------------------------------- /draco-oxide/src/encode/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod header; 2 | pub(crate) mod metadata; 3 | pub(crate) mod connectivity; 4 | pub(crate) mod attribute; 5 | pub(crate) mod entropy; 6 | 7 | use crate::core::mesh::Mesh; 8 | use crate::{debug_write, shared}; 9 | use crate::core::shared::ConfigType; 10 | use crate::core::bit_coder::ByteWriter; 11 | use thiserror::Error; 12 | 13 | #[cfg(feature = "evaluation")] 14 | use crate::eval; 15 | 16 | pub trait EncoderConfig { 17 | type Encoder; 18 | fn get_encoder(&self) -> Self::Encoder; 19 | } 20 | 21 | #[derive(Debug, Clone)] 22 | pub struct Config { 23 | #[allow(unused)] // This field is unused in the current implementation, as we only support edgebreaker. 24 | connectivity_encoder_cfg: connectivity::Config, 25 | #[allow(unused)] // This field is unused in the current implementation, as we only suport the default attribute encoder configuration. 26 | attribute_encoder_cfg: attribute::Config, 27 | geometry_type: header::EncodedGeometryType, 28 | encoder_method: shared::header::EncoderMethod, 29 | metdata: bool 30 | } 31 | 32 | impl ConfigType for Config { 33 | fn default() -> Self { 34 | Self { 35 | connectivity_encoder_cfg: connectivity::Config::default(), 36 | attribute_encoder_cfg: attribute::Config::default(), 37 | geometry_type: header::EncodedGeometryType::TrianglarMesh, 38 | encoder_method: shared::header::EncoderMethod::Edgebreaker, 39 | metdata: false, 40 | } 41 | } 42 | } 43 | 44 | #[remain::sorted] 45 | #[derive(Error, Debug)] 46 | pub enum Err { 47 | #[error("Attribute encoding error: {0}")] 48 | AttributeError(#[from] attribute::Err), 49 | #[error("Connectivity encoding error: {0}")] 50 | ConnectivityError(#[from] connectivity::Err), 51 | #[error("Header encoding error: {0}")] 52 | HeaderError(#[from] header::Err), 53 | #[error("Metadata encoding error: {0}")] 54 | MetadataError(#[from] metadata::Err), 55 | } 56 | 57 | 58 | /// Encodes the input mesh into a provided byte stream using the provided configuration. 59 | pub fn encode(mesh: Mesh, writer: &mut W, cfg: Config) -> Result<(), Err> 60 | where W: ByteWriter 61 | { 62 | #[cfg(feature = "evaluation")] 63 | eval::scope_begin("compression info", writer); 64 | 65 | // Encode header 66 | header::encode_header(writer, &cfg)?; 67 | 68 | debug_write!("Header done, now starting metadata.", writer); 69 | 70 | // Encode metadata 71 | if cfg.metdata { 72 | #[cfg(feature = "evaluation")] 73 | eval::scope_begin("metadata", writer); 74 | metadata::encode_metadata(&mesh, writer)?; 75 | #[cfg(feature = "evaluation")] 76 | eval::scope_end(writer); 77 | } 78 | 79 | 80 | debug_write!("Metadata done, now starting connectivity.", writer); 81 | 82 | // Destruct the mesh so that attributes and faces have the different lifetime. 83 | let Mesh{mut attributes, faces, ..} = mesh; 84 | 85 | // Encode connectivity 86 | let conn_out = connectivity::encode_connectivity(&faces, &mut attributes, writer, &cfg)?; 87 | debug_write!("Connectivity done, now starting attributes.", writer); 88 | 89 | // Encode attributes 90 | attribute::encode_attributes(attributes, writer, conn_out, &cfg)?; 91 | 92 | debug_write!("All done", writer); 93 | 94 | #[cfg(feature = "evaluation")] 95 | eval::scope_end(writer); 96 | Ok(()) 97 | } 98 | -------------------------------------------------------------------------------- /draco-oxide/src/io/gltf/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod encode; 2 | pub mod decode; 3 | pub mod transcoder; 4 | pub mod scene_io; 5 | 6 | -------------------------------------------------------------------------------- /draco-oxide/src/io/gltf/scene_io.rs: -------------------------------------------------------------------------------- 1 | use crate::core::scene::Scene; 2 | use crate::io::gltf::decode::GltfDecoder; 3 | use crate::io::gltf::encode::GltfEncoder; 4 | 5 | #[derive(Debug, thiserror::Error)] 6 | pub enum Err { 7 | #[error("Error: {0}")] 8 | Error(String), 9 | #[error("GLTF Encoder Error: {0}")] 10 | GltfEncoderError(#[from] crate::io::gltf::encode::Err), 11 | #[error("GLTF Decoder Error: {0}")] 12 | GltfDecoderError(#[from] crate::io::gltf::decode::Err), 13 | } 14 | 15 | /// Supported scene file formats 16 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 17 | pub enum SceneFileFormat { 18 | Unknown, 19 | Gltf, 20 | Usd, 21 | Ply, 22 | Obj, 23 | } 24 | 25 | /// Configuration options for scene writing 26 | #[derive(Debug, Default, Clone)] 27 | pub struct SceneWriteOptions { 28 | /// Forces implicit vertex interpolation while exporting to USD 29 | pub force_usd_vertex_interpolation: bool, 30 | } 31 | 32 | /// Reads a scene from a file. Currently only GLTF 2.0 scene files are supported. 33 | pub fn read_scene_from_file(file_name: &str) -> Result { 34 | read_scene_from_file_with_files(file_name, Vec::new()) 35 | } 36 | 37 | /// Reads a scene from a file and optionally returns the files associated with the scene. 38 | pub fn read_scene_from_file_with_files( 39 | file_name: &str, 40 | scene_files: Vec, 41 | ) -> Result { 42 | match get_scene_file_format(file_name) { 43 | SceneFileFormat::Gltf => { 44 | let mut decoder = GltfDecoder::new(); 45 | Ok(decoder.decode_from_file_to_scene_with_files(file_name, scene_files)?) 46 | } 47 | SceneFileFormat::Usd => { 48 | Err(Err::Error(format!("USD is not supported yet."))) 49 | } 50 | _ => { 51 | Err(Err::Error(format!("Unknown input file format."))) 52 | } 53 | } 54 | } 55 | 56 | 57 | /// Writes a scene into a file with configurable options. 58 | /// 59 | /// Supported options: 60 | /// - `force_usd_vertex_interpolation`: forces implicit vertex interpolation 61 | /// while exporting to USD (default = false) 62 | pub fn write_scene_to_file_with_options( 63 | file_name: &str, 64 | scene: &Scene, 65 | _options: &SceneWriteOptions, 66 | ) -> Result<(), Err> { 67 | let (folder_path, _out_file_name) = { 68 | let path = std::path::Path::new(file_name); 69 | let folder_path = path.parent().unwrap_or_else(|| std::path::Path::new(".")); 70 | let out_file_name = path.file_name().and_then(|s| s.to_str()).unwrap_or("output"); 71 | (folder_path.to_string_lossy().to_string(), out_file_name.to_string()) 72 | }; 73 | let format = get_scene_file_format(file_name); 74 | 75 | match format { 76 | SceneFileFormat::Gltf => { 77 | let mut encoder = GltfEncoder::new(); 78 | encoder.encode_scene_to_file(scene, file_name, &folder_path)?; 79 | Ok(()) 80 | } 81 | SceneFileFormat::Usd => { 82 | Err(Err::Error(format!("USD is not supported yet."))) 83 | } 84 | SceneFileFormat::Ply | SceneFileFormat::Obj => { 85 | unimplemented!("Ply and Obj formats are not supported yet."); 86 | // // Convert the scene to mesh and save the scene as a mesh. For now we do 87 | // // that by converting the scene to GLB and decoding the GLB into a mesh. 88 | // let gltf_encoder = GltfEncoder::new(); 89 | // let mut buffer = Vec::::new(); 90 | // gltf_encoder.encode_scene_to_buffer(scene, &mut buffer)?; 91 | 92 | // let gltf_decoder = GltfDecoder::new(); 93 | // let mesh = gltf_decoder.decode_from_buffer(&buffer)?; 94 | 95 | // match format { 96 | // SceneFileFormat::Ply => { 97 | // let ply_encoder = PlyEncoder::new(); 98 | // if !ply_encoder.encode_to_file(&mesh, file_name) { 99 | // return Err(Err::Error(format!("Failed to encode the scene as PLY."))); 100 | // } 101 | // } 102 | // SceneFileFormat::Obj => { 103 | // let obj_encoder = ObjEncoder::new(); 104 | // if !obj_encoder.encode_to_file(&mesh, file_name) { 105 | // return Err(Err::Error(format!("Failed to encode the scene as OBJ."))); 106 | // } 107 | // } 108 | // _ => unreachable!(), 109 | // } 110 | // Ok(()) 111 | } 112 | SceneFileFormat::Unknown => { 113 | Err(Err::Error(format!("Unknown output file format."))) 114 | } 115 | } 116 | } 117 | 118 | /// Determines the scene file format based on the file extension. 119 | pub fn get_scene_file_format(file_name: &str) -> SceneFileFormat { 120 | //get the file extension 121 | let extension = match file_name.rfind('.') { 122 | Some(pos) => &file_name[pos + 1..], 123 | None => return SceneFileFormat::Unknown, 124 | }; 125 | 126 | match extension { 127 | "gltf" | "glb" => SceneFileFormat::Gltf, 128 | "usd" | "usda" | "usdc" | "usdz" => SceneFileFormat::Usd, 129 | "obj" => SceneFileFormat::Obj, 130 | "ply" => SceneFileFormat::Ply, 131 | _ => SceneFileFormat::Unknown, 132 | } 133 | } 134 | 135 | #[cfg(test)] 136 | mod tests { 137 | use super::*; 138 | 139 | #[test] 140 | fn test_get_scene_file_format() { 141 | assert_eq!(get_scene_file_format("model.gltf"), SceneFileFormat::Gltf); 142 | assert_eq!(get_scene_file_format("model.glb"), SceneFileFormat::Gltf); 143 | assert_eq!(get_scene_file_format("model.usd"), SceneFileFormat::Usd); 144 | assert_eq!(get_scene_file_format("model.usda"), SceneFileFormat::Usd); 145 | assert_eq!(get_scene_file_format("model.usdc"), SceneFileFormat::Usd); 146 | assert_eq!(get_scene_file_format("model.usdz"), SceneFileFormat::Usd); 147 | assert_eq!(get_scene_file_format("model.obj"), SceneFileFormat::Obj); 148 | assert_eq!(get_scene_file_format("model.ply"), SceneFileFormat::Ply); 149 | assert_eq!(get_scene_file_format("model.xyz"), SceneFileFormat::Unknown); 150 | } 151 | 152 | #[test] 153 | fn test_scene_write_options_default() { 154 | let options = SceneWriteOptions::default(); 155 | assert!(!options.force_usd_vertex_interpolation); 156 | } 157 | } -------------------------------------------------------------------------------- /draco-oxide/src/io/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod obj; 2 | pub mod gltf; 3 | pub mod texture_io; -------------------------------------------------------------------------------- /draco-oxide/src/io/obj/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::core::attribute::AttributeDomain; 2 | // use tobj to load the obj file and convert it to our internal mesh representation 3 | use crate::prelude::{AttributeType, MeshBuilder, NdVector}; 4 | use crate::prelude::Mesh; 5 | use std::fmt::Debug; 6 | use std::path::Path; 7 | 8 | #[derive(Debug, thiserror::Error, Clone)] 9 | pub enum Err { 10 | #[error("Mesh Builder Error: {0}")] 11 | MeshBuilderError(#[from] crate::core::mesh::builder::Err), 12 | } 13 | 14 | pub fn load_obj + Debug>(path: P) -> Result { 15 | let op = tobj::LoadOptions { 16 | triangulate: true, 17 | single_index: true, 18 | ..Default::default() 19 | }; 20 | 21 | let (models, _materials) = tobj::load_obj(path, &op).expect("Failed to load OBJ file"); 22 | let model: &tobj::Model = &models[0]; 23 | let pos = model.mesh.positions.chunks(3) 24 | .map(|x| NdVector::from([x[0] as f32, x[1] as f32, x[2] as f32])) 25 | .collect::>(); 26 | let faces = model.mesh.indices.chunks(3) 27 | .map(|x| [x[0] as usize, x[1] as usize, x[2] as usize]) 28 | .collect::>(); 29 | let (normals, normals_domain_ty) = load_normals(&model.mesh); 30 | let (tex_coords, tex_coords_domain_ty) = load_tex_coords(&model.mesh); 31 | let mut builder = MeshBuilder::new(); 32 | builder.set_connectivity_attribute(faces); 33 | let pos_att_id = builder.add_attribute(pos, AttributeType::Position, AttributeDomain::Position, vec![]); 34 | if !normals.is_empty() { 35 | builder.add_attribute(normals, AttributeType::Normal, normals_domain_ty, vec![pos_att_id]); 36 | } 37 | if !tex_coords.is_empty() { 38 | builder.add_attribute(tex_coords, AttributeType::TextureCoordinate, tex_coords_domain_ty, vec![pos_att_id]); 39 | } 40 | 41 | Ok(builder.build()?) 42 | } 43 | 44 | fn load_normals(mesh: &tobj::Mesh) -> (Vec>, AttributeDomain) { 45 | if mesh.normals.is_empty() { 46 | return (vec![], AttributeDomain::Position) 47 | } 48 | let normals = mesh.normals.chunks(3) 49 | .map(|x| NdVector::from([x[0] as f32, x[1] as f32, x[2] as f32])) 50 | .collect::>(); 51 | (normals, AttributeDomain::Corner) 52 | } 53 | 54 | 55 | fn load_tex_coords(mesh: &tobj::Mesh) -> (Vec>, AttributeDomain) { 56 | if mesh.texcoords.is_empty() { 57 | return (vec![], AttributeDomain::Position) 58 | } 59 | let tex_coords = mesh.texcoords.chunks(2) 60 | .map(|x| NdVector::from([x[0] as f32, x[1] as f32])) 61 | .collect::>(); 62 | 63 | (tex_coords, AttributeDomain::Corner) 64 | } 65 | 66 | #[cfg(test)] 67 | mod tests { 68 | use crate::core::shared::PointIdx; 69 | 70 | use super::*; 71 | 72 | #[test] 73 | fn tetrahedron() { 74 | let mesh = load_obj("tests/data/tetrahedron.obj").unwrap(); 75 | assert_eq!(mesh.get_faces(), 76 | vec![ 77 | [PointIdx::from(0), PointIdx::from(1), PointIdx::from(2)], 78 | [PointIdx::from(0), PointIdx::from(3), PointIdx::from(1)], 79 | [PointIdx::from(0), PointIdx::from(2), PointIdx::from(4)], 80 | [PointIdx::from(1), PointIdx::from(5), PointIdx::from(2)] 81 | ] 82 | ); 83 | assert_eq!(mesh.attributes.len(), 3); 84 | assert_eq!(mesh.attributes[0].get_attribute_type(), AttributeType::Position); 85 | assert_eq!(mesh.attributes[0].get_domain(), AttributeDomain::Position); 86 | assert_eq!(mesh.attributes[0].get_num_components(), 3); 87 | assert_eq!(mesh.attributes[0].num_unique_values(), 4); 88 | assert_eq!(mesh.attributes[0].len(), 6); 89 | } 90 | } -------------------------------------------------------------------------------- /draco-oxide/src/io/texture_io.rs: -------------------------------------------------------------------------------- 1 | /// Texture I/O functionality for reading and writing texture data. 2 | /// Based on the Draco C++ implementation in draco/io/texture_io.h 3 | 4 | use crate::core::texture::{Texture, ImageFormat}; 5 | use std::fs; 6 | 7 | #[derive(Debug, thiserror::Error)] 8 | pub enum Err { 9 | #[error("IO Error: {0}")] 10 | IoError(String), 11 | #[error("Invalid format: {0}")] 12 | InvalidFormat(String), 13 | #[error("Decode Error: {0}")] 14 | DecodeError(String), 15 | } 16 | 17 | 18 | 19 | 20 | /// Writes a texture into a buffer. 21 | pub(crate) fn write_texture_to_buffer(texture: &Texture, buffer: &mut Vec) -> Result<(), Err> { 22 | let source_image = texture.get_source_image(); 23 | 24 | // If we have encoded data, use it directly 25 | if !source_image.get_encoded_data().is_empty() { 26 | buffer.extend_from_slice(source_image.get_encoded_data()); 27 | return Ok(()); 28 | } 29 | 30 | // If we have a filename but no encoded data, try to read from file 31 | if !source_image.get_filename().is_empty() { 32 | let file_data = fs::read(source_image.get_filename()).map_err(|e| { 33 | Err::IoError(format!("Failed to read texture file '{}': {}", source_image.get_filename(), e)) 34 | })?; 35 | buffer.extend_from_slice(&file_data); 36 | return Ok(()); 37 | } 38 | 39 | Err(Err::InvalidFormat("Texture has no encoded data or filename".to_string())) 40 | } 41 | 42 | /// Returns the image format of an encoded texture stored in buffer. 43 | /// ImageFormat::None is returned for unknown image formats. 44 | pub fn image_format_from_buffer(buffer: &[u8]) -> ImageFormat { 45 | if buffer.len() > 4 { 46 | // JPEG markers 47 | let jpeg_soi_marker = [0xFF, 0xD8]; 48 | let jpeg_eoi_marker = [0xFF, 0xD9]; 49 | 50 | if buffer.starts_with(&jpeg_soi_marker) { 51 | // Look for the end marker (allow trailing bytes) 52 | if buffer.windows(2).any(|window| window == jpeg_eoi_marker) { 53 | return ImageFormat::Jpeg; 54 | } 55 | } 56 | } 57 | 58 | if buffer.len() > 2 { 59 | // Basis format signature 'B' * 256 + 's', or 0x4273 60 | let basis_signature = [0x42, 0x73]; 61 | if buffer.starts_with(&basis_signature) { 62 | return ImageFormat::Basis; 63 | } 64 | } 65 | 66 | if buffer.len() > 4 { 67 | // KTX2 format signature 0xab 0x4b 0x54 0x58 68 | let ktx2_signature = [0xab, 0x4b, 0x54, 0x58]; 69 | if buffer.starts_with(&ktx2_signature) { 70 | return ImageFormat::Basis; 71 | } 72 | } 73 | 74 | if buffer.len() > 8 { 75 | // PNG signature 76 | let png_signature = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]; 77 | if buffer.starts_with(&png_signature) { 78 | return ImageFormat::Png; 79 | } 80 | } 81 | 82 | if buffer.len() > 12 { 83 | // WebP signature: RIFF followed by size (4 bytes) then WEBP 84 | let riff = [0x52, 0x49, 0x46, 0x46]; 85 | let webp = [0x57, 0x45, 0x42, 0x50]; 86 | 87 | if buffer.starts_with(&riff) && buffer.len() > 8 && &buffer[8..12] == webp { 88 | return ImageFormat::Webp; 89 | } 90 | } 91 | 92 | ImageFormat::None 93 | } 94 | 95 | #[cfg(test)] 96 | mod tests { 97 | use super::*; 98 | 99 | #[test] 100 | fn test_image_format_detection() { 101 | // Test PNG 102 | let png_data = [0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00]; 103 | assert_eq!(image_format_from_buffer(&png_data), ImageFormat::Png); 104 | 105 | // Test JPEG 106 | let jpeg_data = [0xFF, 0xD8, 0x00, 0x00, 0xFF, 0xD9]; 107 | assert_eq!(image_format_from_buffer(&jpeg_data), ImageFormat::Jpeg); 108 | 109 | // Test unknown format 110 | let unknown_data = [0x00, 0x01, 0x02, 0x03]; 111 | assert_eq!(image_format_from_buffer(&unknown_data), ImageFormat::None); 112 | } 113 | } -------------------------------------------------------------------------------- /draco-oxide/src/lib.rs: -------------------------------------------------------------------------------- 1 | // lib.rs 2 | 3 | /// Contains the interface between `Mesh` object and 3D geometry files 4 | /// such as obj and gltf. 5 | pub mod io; 6 | 7 | /// Contains compression techniques used by the encoder and the decoder. 8 | pub(crate) mod shared; 9 | 10 | /// Defines the mesh encoder. 11 | pub mod encode; 12 | 13 | // /// Defines the decoders. 14 | // pub mod decode; 15 | 16 | /// Contains the shared definitions, native objects, and the buffer. 17 | pub(crate) mod core; 18 | 19 | /// Contains the macros used by the encoder and the decoder. 20 | pub(crate) mod utils; 21 | 22 | 23 | /// Contains the most commonly used traits, types, and objects. 24 | pub mod prelude { 25 | pub use crate::core::attribute::{Attribute, AttributeType}; 26 | pub use crate::core::mesh::{Mesh, builder::MeshBuilder}; 27 | pub use crate::core::shared::{NdVector, Vector, DataValue}; 28 | pub use crate::core::shared::ConfigType; 29 | pub use crate::core::bit_coder::{ 30 | ByteReader, 31 | ByteWriter, 32 | FunctionalByteReader, 33 | FunctionalByteWriter 34 | }; 35 | pub use crate::encode::{self, encode}; 36 | // pub use crate::decode::{self, decode}; 37 | } 38 | 39 | 40 | /// Evaluation module contains the evaluation functions for the encoder and the decoder. 41 | /// When enabled, draco-oxide encoder will spit out the evaluation data mixed with encoded data, 42 | /// and then the `EvalWriter` is used to filter out the evaluation data. This functionality is 43 | /// most often used in the development and testing phase. 44 | #[cfg(feature = "evaluation")] 45 | pub mod eval; -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::core::bit_coder::ReaderErr; 2 | use crate::prelude::{ByteReader, ByteWriter}; 3 | 4 | pub(crate) mod prediction_scheme; 5 | pub(crate) mod portabilization; 6 | pub mod sequence; 7 | 8 | #[derive(thiserror::Error, Debug)] 9 | #[allow(unused)] 10 | pub enum Err { 11 | #[error("Reader Error: {0}")] 12 | ReaderError(#[from] ReaderErr), 13 | } 14 | 15 | pub trait Portable: Sized { 16 | fn to_bytes(self) -> Vec; 17 | fn write_to(self, writer: &mut W) where W: ByteWriter; 18 | fn read_from(reader: &mut R) -> Result 19 | where R: ByteReader; 20 | } 21 | 22 | 23 | impl Portable for bool { 24 | fn to_bytes(self) -> Vec { 25 | vec![self as u8] 26 | } 27 | fn write_to(self, writer: &mut W) where W: ByteWriter { 28 | writer.write_u8(self as u8); 29 | } 30 | fn read_from(reader: &mut R) -> Result 31 | where R: ByteReader 32 | { 33 | Ok(reader.read_u8()? != 0) 34 | } 35 | } 36 | 37 | 38 | #[cfg(test)] 39 | mod tests { 40 | use crate::prelude::NdVector; 41 | use super::Portable; 42 | 43 | #[test] 44 | fn from_bits_f32() { 45 | let data = NdVector::from([1_f32, -1.0, 1.0]); 46 | let mut buff_writer = Vec::new(); 47 | data.write_to(&mut buff_writer); 48 | let mut buff_reader = buff_writer.into_iter(); 49 | let dequant_data: NdVector<3,f32> = NdVector::read_from(&mut buff_reader).unwrap(); 50 | assert_eq!(data, dequant_data); 51 | } 52 | 53 | #[test] 54 | fn from_bits_f64() { 55 | let data = NdVector::from([1_f64, -1.0, 1.0]); 56 | let mut buff_writer = Vec::new(); 57 | data.write_to(&mut buff_writer); 58 | let mut buff_reader = buff_writer.into_iter(); 59 | let dequant_data: NdVector<3,f64> = NdVector::read_from(&mut buff_reader).unwrap(); 60 | assert_eq!(data, dequant_data); 61 | } 62 | } -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/portabilization/mod.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/src/shared/attribute/portabilization/mod.rs -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/portabilization/spherical.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/src/shared/attribute/portabilization/spherical.rs -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/prediction_scheme/delta_prediction.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::{attribute::Attribute, corner_table::GenericCornerTable, shared::{CornerIdx, VertexIdx}}, prelude::{NdVector, Vector}}; 2 | use super::PredictionSchemeImpl; 3 | use std::mem; 4 | 5 | pub struct DeltaPrediction<'parents, C, const N: usize> 6 | { 7 | faces: &'parents [[usize; 3]], 8 | corner_table: &'parents C, 9 | _marker: std::marker::PhantomData<&'parents C>, 10 | } 11 | 12 | 13 | impl<'parents, C, const N: usize> PredictionSchemeImpl<'parents, C, N> for DeltaPrediction<'parents, C, N> 14 | where C: GenericCornerTable, 15 | NdVector: Vector, 16 | { 17 | const ID: u32 = 1; 18 | 19 | type AdditionalDataForMetadata = (); 20 | 21 | fn new(_parents: &[&'parents Attribute], corner_table: &'parents C) -> Self { 22 | // Note: Connectivity is now passed via conn_att parameter instead of parent attributes 23 | // For now, use an empty slice as this prediction scheme needs to be updated for the new architecture 24 | let faces: &[[usize; 3]] = &[]; 25 | 26 | Self { 27 | faces, 28 | corner_table, 29 | _marker: std::marker::PhantomData, 30 | } 31 | } 32 | 33 | fn get_values_impossible_to_predict(&mut self, value_indices: &mut Vec>) 34 | -> Vec> 35 | { 36 | let mut self_ = Vec::new(); 37 | let mut out = Vec::new(); 38 | for r in value_indices.iter() { 39 | for i in r.clone() { 40 | if i == 0 { 41 | out.push(i); 42 | } 43 | // ToDo: Optimize this: 'self.faces' are the sorted array of sorted arrays. 44 | else if self.faces.iter().any(|f|f.contains(&(i-1))&&f.contains(&i)) { 45 | self_.push(i); 46 | } else { 47 | out.push(i); 48 | } 49 | } 50 | } 51 | let mut self_ = into_ranges(self_); 52 | mem::swap(&mut self_, value_indices); 53 | into_ranges(out) 54 | } 55 | 56 | fn predict( 57 | &mut self, 58 | _i: CornerIdx, 59 | vertices_up_till_now: &[VertexIdx], 60 | att: &Attribute, 61 | ) -> NdVector 62 | { 63 | let prev_v = if let Some(prev_v) = vertices_up_till_now.last() { 64 | *prev_v 65 | } else { 66 | // If there are no previous vertices, we cannot predict the value. 67 | return NdVector::zero(); 68 | }; 69 | let prev_pt = self.corner_table.point_idx(self.corner_table.left_most_corner(prev_v)); 70 | att.get(prev_pt) 71 | } 72 | } 73 | 74 | fn into_ranges(v: Vec) -> Vec> { 75 | let mut out = Vec::new(); 76 | if v.is_empty() { 77 | return out; 78 | } 79 | let mut start = v[0]; 80 | let mut end = v[0]; 81 | for &val in &v[1..] { 82 | if val != end + 1 { 83 | out.push(start..end + 1); 84 | start = val; 85 | } 86 | end = val; 87 | } 88 | out.push(start..end + 1); 89 | out 90 | } 91 | 92 | // ToDo: recover this test. 93 | // #[cfg(test)] 94 | // mod tests { 95 | // use crate::{core::attribute::AttributeId, prelude::NdVector}; 96 | 97 | // use super::*; 98 | 99 | // #[test] 100 | // fn test_into_ranges() { 101 | // let v = vec![1, 3, 6, 7, 8, 10, 11, 12, 15]; 102 | // let r = into_ranges(v); 103 | // assert_eq!(r.len(), 5); 104 | // assert_eq!(r[0], 1..2); 105 | // assert_eq!(r[1], 3..4); 106 | // assert_eq!(r[2], 6..9); 107 | // assert_eq!(r[3], 10..13); 108 | // assert_eq!(r[4], 15..16); 109 | // } 110 | 111 | // #[test] 112 | // fn test_get_values_impossible_to_predict() { 113 | // let faces = vec![[0, 1, 2], [1, 2, 3], [4, 5, 6], [5, 6, 7]]; 114 | // let conn_att = Attribute::from_faces( 115 | // AttributeId::new(0), 116 | // faces.clone(), 117 | // Vec::new() 118 | // ); 119 | // let mut delta = DeltaPrediction::>::new(&[&conn_att]); 120 | // let mut value_indices = vec![0..8]; 121 | // let impossible = delta.get_values_impossible_to_predict(&mut value_indices); 122 | // assert_eq!(impossible.len(), 2); 123 | // assert_eq!(impossible[0], 0..1); 124 | // assert_eq!(impossible[1], 4..5); 125 | 126 | // assert_eq!(value_indices.len(), 2); 127 | // assert_eq!(value_indices[0], 1..4); 128 | // assert_eq!(value_indices[1], 5..8); 129 | // } 130 | // } -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/prediction_transform/difference.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::Vector; 2 | 3 | use super::{FinalMetadata, PredictionTransform}; 4 | use crate::core::shared::Max; 5 | 6 | 7 | pub struct Difference { 8 | _out: Vec, 9 | _metadata: Data, 10 | } 11 | 12 | impl Difference 13 | where Data: Vector 14 | { 15 | pub fn new() -> Self { 16 | let mut _metadata = Data::zero(); 17 | for i in 0..Data::NUM_COMPONENTS { 18 | // Safety: 19 | // iterating over a constant-sized array 20 | unsafe{ 21 | *_metadata.get_unchecked_mut(i) = Data::Component::MAX_VALUE; 22 | } 23 | } 24 | Self { 25 | _out: Vec::new(), 26 | _metadata, 27 | } 28 | } 29 | } 30 | 31 | impl PredictionTransform for Difference 32 | where Data: Vector 33 | { 34 | const ID: usize = 1; 35 | 36 | type Data = Data; 37 | type Correction = Data; 38 | type Metadata = Data; 39 | 40 | fn map(orig: Self::Data, pred: Self::Data, metadata: Self::Metadata) -> Self::Correction { 41 | orig - pred - metadata 42 | } 43 | 44 | fn map_with_tentative_metadata(&mut self, orig: Self::Data, pred: Self::Data) { 45 | let corr = orig - pred; 46 | self._out.push(corr); 47 | // update metadata 48 | for i in 0..Data::NUM_COMPONENTS { 49 | unsafe{ 50 | if self._metadata.get_unchecked(i) > corr.get_unchecked(i) { 51 | *self._metadata.get_unchecked_mut(i) = *corr.get_unchecked(i); 52 | } 53 | } 54 | } 55 | } 56 | 57 | fn inverse(&mut self, pred: Self::Data, crr: Self::Correction, metadata: Self::Metadata) -> Self::Data { 58 | pred + crr + metadata 59 | } 60 | 61 | fn squeeze(&mut self) -> (FinalMetadata, Vec) { 62 | self._out.iter_mut() 63 | .for_each(|v| 64 | *v -= self._metadata 65 | ); 66 | ( 67 | FinalMetadata::Global(self._metadata), 68 | std::mem::take(&mut self._out) 69 | ) 70 | } 71 | } 72 | 73 | 74 | 75 | #[cfg(test)] 76 | mod tests { 77 | use super::*; 78 | use crate::core::shared::NdVector; 79 | 80 | #[test] 81 | fn test_transform() { 82 | let mut transform = Difference::>::new(); 83 | let orig1 = NdVector::<3, f64>::from([1.0, 2.0, 3.0]); 84 | let pred1 = NdVector::<3, f64>::from([1.0, 1.0, 1.0]); 85 | let orig2 = NdVector::<3, f64>::from([4.0, 5.0, 6.0]); 86 | let pred2 = NdVector::<3, f64>::from([5.0, 5.0, 5.0]); 87 | 88 | transform.map_with_tentative_metadata(orig1.clone(), pred1.clone()); 89 | transform.map_with_tentative_metadata(orig2.clone(), pred2.clone()); 90 | 91 | let (final_metadata, corrections) = transform.squeeze(); 92 | let final_metadata = match final_metadata { 93 | FinalMetadata::Local(_) => panic!("Expected global metadata"), 94 | FinalMetadata::Global(m) => m, 95 | }; 96 | let metadata = NdVector::<3, f64>::from([-1.0, 0.0, 1.0]); 97 | assert_eq!(final_metadata, metadata); 98 | let recovered1 = transform.inverse(pred1.clone(), corrections[0], final_metadata); 99 | let recovered2 = transform.inverse(pred2.clone(), corrections[1], final_metadata); 100 | assert_eq!(recovered1, orig1); 101 | assert_eq!(recovered2, orig2); 102 | } 103 | } -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/prediction_transform/geom.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{DataValue, NdVector, Vector}; 2 | 3 | pub(super) fn rotation_matrix_from(axis: Data, angle: f64) -> [Data; 3] 4 | where 5 | Data: Vector, 6 | Data::Component: DataValue 7 | { 8 | let cos_angle = Data::Component::from_f64(angle.cos()); 9 | let sin_angle = Data::Component::from_f64(angle.sin()); 10 | let one_minus_cos = Data::Component::one() - cos_angle; 11 | let mut r1 = Data::zero(); 12 | let mut r2 = Data::zero(); 13 | let mut r3 = Data::zero(); 14 | unsafe { 15 | *r1.get_unchecked_mut(0) = cos_angle + *axis.get_unchecked(0) * *axis.get_unchecked(0) * one_minus_cos; 16 | *r1.get_unchecked_mut(1) = *axis.get_unchecked(0) * *axis.get_unchecked(1) * one_minus_cos - *axis.get_unchecked(2); 17 | *r1.get_unchecked_mut(2) = *axis.get_unchecked(0) * *axis.get_unchecked(2) * one_minus_cos + *axis.get_unchecked(1); 18 | 19 | *r2.get_unchecked_mut(0) = *axis.get_unchecked(1) * *axis.get_unchecked(0) * one_minus_cos + *axis.get_unchecked(2) * sin_angle; 20 | *r2.get_unchecked_mut(1) = cos_angle + *axis.get_unchecked(1) * *axis.get_unchecked(1) * one_minus_cos; 21 | *r2.get_unchecked_mut(2) = *axis.get_unchecked(1) * *axis.get_unchecked(2) * one_minus_cos - *axis.get_unchecked(0) * sin_angle; 22 | 23 | *r3.get_unchecked_mut(0) = *axis.get_unchecked(2) * *axis.get_unchecked(0) * one_minus_cos - *axis.get_unchecked(1) * sin_angle; 24 | *r3.get_unchecked_mut(1) = *axis.get_unchecked(2) * *axis.get_unchecked(1) * one_minus_cos + *axis.get_unchecked(0) * sin_angle; 25 | *r3.get_unchecked_mut(2) = cos_angle + *axis.get_unchecked(2) * *axis.get_unchecked(2) * one_minus_cos; 26 | }; 27 | [ 28 | r1, 29 | r2, 30 | r3, 31 | ] 32 | } 33 | 34 | 35 | 36 | use crate::core::shared::Abs; 37 | /// Transforms the data to the octahedron space. 38 | /// Make sure that the data is three dimensional. 39 | pub(super) unsafe fn octahedral_transform(v: Data) -> NdVector<2, f64> 40 | where 41 | Data: Vector, 42 | Data::Component: DataValue 43 | { 44 | let x = v.get_unchecked(0); 45 | let y = v.get_unchecked(1); 46 | let z = v.get_unchecked(2); 47 | 48 | let abs_sum = x.abs() + y.abs() + z.abs(); 49 | 50 | let mut u = *x / abs_sum; 51 | let mut v = *y / abs_sum; 52 | 53 | if *z < Data::Component::zero() { 54 | let one = Data::Component::one(); 55 | let minus_one = Data::Component::zero() - one; 56 | let u_sign = if u > Data::Component::zero() { 57 | one 58 | } else { 59 | minus_one 60 | }; 61 | let v_sign = if v > Data::Component::zero() { 62 | one 63 | } else { 64 | minus_one 65 | }; 66 | (u, v) = ( 67 | (Data::Component::one() - v.abs()) * u_sign, 68 | (Data::Component::one() - u.abs()) * v_sign 69 | ); 70 | } 71 | 72 | let mut out = NdVector::<2, _>::zero(); 73 | unsafe { 74 | *out.get_unchecked_mut(0) = u.to_f64(); 75 | *out.get_unchecked_mut(1) = v.to_f64(); 76 | } 77 | 78 | out 79 | } 80 | 81 | 82 | /// Data is transformed back from the octahedron space. 83 | /// Safety: 84 | /// 'Data' must be three dimensional. 85 | pub(super) unsafe fn octahedral_inverse_transform(v: NdVector<2, f64>) -> Data 86 | where 87 | Data: Vector, 88 | Data::Component: DataValue 89 | { 90 | let u = v.get_unchecked(0); 91 | let v = v.get_unchecked(1); 92 | 93 | let mut x = *u; 94 | let mut y = *v; 95 | let z = 1.0 - u.abs() - v.abs(); 96 | 97 | if u.abs()+v.abs() > 1.0 { 98 | let x_sign = if x > 0.0 { 99 | 1.0 100 | } else { 101 | -1.0 102 | }; 103 | let y_sign = if y > 0.0 { 104 | 1.0 105 | } else { 106 | -1.0 107 | }; 108 | x = (1.0 - v.abs()) * x_sign; 109 | y = (1.0 - u.abs()) * y_sign; 110 | } 111 | 112 | // normalize the vector 113 | let norm = (x*x + y*y + z*z).sqrt(); 114 | 115 | let mut out = Data::zero(); 116 | // safety condition is upheld 117 | *out.get_unchecked_mut(0) = Data::Component::from_f64(x/norm); 118 | *out.get_unchecked_mut(1) = Data::Component::from_f64(y/norm); 119 | *out.get_unchecked_mut(2) = Data::Component::from_f64(z/norm); 120 | 121 | out 122 | } 123 | 124 | 125 | #[cfg(test)] 126 | mod tests { 127 | use super::*; 128 | use crate::core::shared::NdVector; 129 | use crate::core::shared::Dot; 130 | 131 | #[test] 132 | fn test_octahedral_transform() { 133 | let vs = { 134 | vec![ 135 | NdVector::from([1_f64, 0.0, 0.0]), 136 | NdVector::from([0.0, 1.0, 0.0]), 137 | NdVector::from([0.0, 0.0, 1.0]), 138 | NdVector::from([-1.0, 0.0, 0.0]), 139 | NdVector::from([0.0, -1.0, 0.0]), 140 | NdVector::from([0.0, 0.0, -1.0]), 141 | NdVector::from([1.0, 1.0, 1.0]), 142 | NdVector::from([-1.0, -1.0, -1.0]), 143 | NdVector::from([1.0, -1.0, 1.0]), 144 | NdVector::from([-1.0, 1.0, -1.0]), 145 | NdVector::from([1.0, 1.0, -1.0]), 146 | NdVector::from([-1.0, -1.0, 1.0]), 147 | NdVector::from([1.0, -1.0, -1.0]), 148 | ] 149 | }; 150 | for v in vs { 151 | // normalize the vector 152 | let n = v / v.dot(v).sqrt(); 153 | // Safety: 154 | // inputs are three dimensional 155 | let transformed = unsafe { octahedral_transform(n) }; 156 | let recovered = unsafe { octahedral_inverse_transform(transformed) }; 157 | let diff = n - recovered; 158 | let diff_norm_squared = diff.dot(diff); 159 | assert!(diff_norm_squared < 1e-10, "Difference is too large: {}, v={:?}, recovered={:?}", diff_norm_squared, v, recovered); 160 | } 161 | } 162 | } -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/prediction_transform/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod difference; 2 | mod geom; 3 | pub mod orthogonal; 4 | pub mod oct_orthogonal; 5 | pub mod oct_reflection; 6 | pub mod oct_difference; 7 | 8 | use core::fmt; 9 | use std::cmp; 10 | 11 | use crate::core::shared::{ConfigType, Vector}; 12 | 13 | pub(crate) trait PredictionTransform { 14 | const ID: usize = 0; 15 | 16 | type Data: Vector; 17 | type Correction: Vector + Copy; // examine if Copy is needed and remove it if not 18 | type Metadata; 19 | 20 | /// transforms the data (the correction value) with the given metadata. 21 | fn map(orig: Self::Data, pred: Self::Data, metadata: Self::Metadata) -> Self::Correction; 22 | 23 | /// transforms the data (the correction value) with the tentative metadata value. 24 | /// The tentative metadata can be determined by the function without any restriction, 25 | /// but it needs to be returned. The output of the transform might get later on 26 | /// fixed by the metadata universal to the attribute after all the transforms are 27 | /// done once for each attribute value. 28 | fn map_with_tentative_metadata(&mut self, orig: Self::Data, pred: Self::Data); 29 | 30 | /// The inverse transform revertes 'map()'. 31 | fn inverse(&mut self, pred: Self::Data, crr: Self::Correction, metadata: Self::Metadata) -> Self::Data; 32 | 33 | /// squeezes the transform results having computed the entire attribute and 34 | /// gives up the final data. 35 | /// This includes cutting off the unnecessary data from both tentative metadata 36 | /// and the transformed data, or doing some trade-off's between the tentative 37 | /// metadata and the transformed data to decide the global metadata that will 38 | /// be encoded to buffer. 39 | fn squeeze(&mut self) -> (FinalMetadata, Vec); 40 | } 41 | 42 | 43 | #[derive(Clone)] 44 | /// The final metadata is either local or global. Local metadata 45 | /// is stored for each attribute value, while global metadata is stored 46 | /// once for the entire attribute. 47 | pub(crate) enum FinalMetadata { 48 | Local(Vec), 49 | Global(T) 50 | } 51 | 52 | impl fmt::Debug for FinalMetadata 53 | where T: fmt::Debug 54 | { 55 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 56 | match self { 57 | FinalMetadata::Local(x) => write!(f, "Local({:?})", x), 58 | FinalMetadata::Global(x) => write!(f, "Global({:?})", x), 59 | } 60 | } 61 | } 62 | 63 | impl cmp::PartialEq for FinalMetadata 64 | where T: cmp::PartialEq 65 | { 66 | fn eq(&self, other: &Self) -> bool { 67 | match (self, other) { 68 | (FinalMetadata::Local(x), FinalMetadata::Local(y)) => x == y, 69 | (FinalMetadata::Global(x), FinalMetadata::Global(y)) => x == y, 70 | _ => false, 71 | } 72 | } 73 | } 74 | 75 | /// Trait limiting the selections of the encoding methods for vertex coordinates. 76 | trait TransformForVertexCoords: PredictionTransform {} 77 | 78 | /// Trait limiting the selections of the encoding methods for texture coordinates. 79 | trait TransformForTexCoords: PredictionTransform {} 80 | 81 | /// Trait limiting the selections of the encoding methods for normals. 82 | trait TansformForNormals: PredictionTransform {} 83 | 84 | #[derive(Clone, Copy)] 85 | pub enum PredictionTransformType { 86 | Difference, 87 | OctahedralDifference, 88 | OctahedralReflection, 89 | OctahedralOrthogonal, 90 | Orthogonal, 91 | NoTransform, 92 | } 93 | 94 | #[derive(Clone, Copy)] 95 | pub struct Config { 96 | pub prediction_transform: PredictionTransformType, 97 | } 98 | 99 | impl ConfigType for Config { 100 | fn default()-> Self { 101 | Config { 102 | prediction_transform: PredictionTransformType::Difference, 103 | } 104 | } 105 | } 106 | 107 | 108 | pub struct NoPredictionTransform { 109 | _marker: std::marker::PhantomData, 110 | } 111 | 112 | impl PredictionTransform for NoPredictionTransform 113 | where 114 | Data: Vector, 115 | { 116 | const ID: usize = 0; 117 | type Data = Data; 118 | type Correction = Data; 119 | type Metadata = (); 120 | fn map(_orig: Self::Data, _pred: Self::Data, _metadata: Self::Metadata) -> Self::Correction{ 121 | unreachable!() 122 | } 123 | fn map_with_tentative_metadata(&mut self, _orig: Self::Data, _pred: Self::Data) { 124 | unreachable!() 125 | } 126 | fn inverse(&mut self, _pred: Self::Data, _crr: Self::Correction, _metadata: Self::Metadata) -> Self::Data { 127 | unreachable!() 128 | } 129 | fn squeeze(&mut self) -> (FinalMetadata, Vec) { 130 | unreachable!() 131 | } 132 | } -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/prediction_transform/oct_difference.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{DataValue, NdVector, Vector}; 2 | use super::geom::{ 3 | octahedral_inverse_transform, 4 | octahedral_transform 5 | }; 6 | 7 | use super::{FinalMetadata, PredictionTransform}; 8 | 9 | 10 | pub struct OctahedronDifferenceTransform { 11 | _out: Vec>, 12 | _marker: std::marker::PhantomData, 13 | } 14 | 15 | impl OctahedronDifferenceTransform 16 | where Data: Vector 17 | { 18 | pub fn new() -> Self { 19 | Self { 20 | _out: Vec::new(), 21 | _marker: std::marker::PhantomData, 22 | } 23 | } 24 | } 25 | 26 | impl PredictionTransform for OctahedronDifferenceTransform 27 | where 28 | Data: Vector, 29 | Data::Component: DataValue 30 | { 31 | const ID: usize = 2; 32 | 33 | type Data = Data; 34 | type Correction = NdVector<2,f64>; 35 | type Metadata = (); 36 | 37 | fn map(_orig: Self::Data, _pred: Self::Data, _: Self::Metadata) -> Self::Correction { 38 | unimplemented!() 39 | } 40 | 41 | fn map_with_tentative_metadata(&mut self, mut orig: Self::Data, mut pred: Self::Data) { 42 | // Safety: 43 | // We made sure that the data is three dimensional. 44 | debug_assert!( 45 | Data::NUM_COMPONENTS == 3, 46 | ); 47 | 48 | let orig = unsafe{ octahedral_transform(orig) }; 49 | let pred = unsafe { octahedral_transform(pred) }; 50 | self._out.push( orig - pred ); 51 | } 52 | 53 | fn inverse(&mut self, mut pred: Self::Data, crr: Self::Correction, _: Self::Metadata) -> Self::Data { 54 | // Safety: 55 | // We made sure that the data is three dimensional. 56 | debug_assert!( 57 | Data::NUM_COMPONENTS == 3, 58 | ); 59 | 60 | let pred_in_oct = unsafe { 61 | octahedral_transform(pred) 62 | }; 63 | 64 | let orig = pred_in_oct + crr; 65 | 66 | // Safety: 67 | // We made sure that the data is three dimensional. 68 | unsafe { 69 | octahedral_inverse_transform(orig) 70 | } 71 | } 72 | 73 | fn squeeze(&mut self) -> (FinalMetadata, Vec) { 74 | ( 75 | FinalMetadata::Global(()), 76 | std::mem::take(&mut self._out) 77 | ) 78 | } 79 | } 80 | 81 | 82 | #[cfg(test)] 83 | mod tests { 84 | use super::*; 85 | use crate::core::shared::NdVector; 86 | use crate::core::shared::Abs; 87 | 88 | #[test] 89 | fn test_transform() { 90 | let mut transform = OctahedronDifferenceTransform::>::new(); 91 | let orig1 = NdVector::<3, f64>::from([1.0, 2.0, 3.0]).normalize(); 92 | let pred1 = NdVector::<3, f64>::from([1.0, 1.0, 1.0]).normalize(); 93 | let orig2 = NdVector::<3, f64>::from([4.0, 5.0, 6.0]).normalize(); 94 | let pred2 = NdVector::<3, f64>::from([5.0, 5.0, 5.0]).normalize(); 95 | 96 | transform.map_with_tentative_metadata(orig1.clone(), pred1.clone()); 97 | transform.map_with_tentative_metadata(orig2.clone(), pred2.clone()); 98 | 99 | let (_, corrections) = transform.squeeze(); 100 | let recovered1 = transform.inverse(pred1.clone(), corrections[0], ()); 101 | let recovered2 = transform.inverse(pred2.clone(), corrections[1], ()); 102 | assert!((recovered1 - orig1).norm() < 0.000_000_1); 103 | assert!((recovered2 - orig2).norm() < 0.000_000_1); 104 | } 105 | } -------------------------------------------------------------------------------- /draco-oxide/src/shared/attribute/prediction_transform/oct_reflection.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{DataValue, NdVector, Vector}; 2 | use super::geom::{ 3 | octahedral_inverse_transform, 4 | octahedral_transform 5 | }; 6 | 7 | use super::{FinalMetadata, PredictionTransform}; 8 | 9 | 10 | pub struct OctahedronReflectionTransform { 11 | _out: Vec>, 12 | _marker: std::marker::PhantomData, 13 | } 14 | 15 | impl OctahedronReflectionTransform 16 | where Data: Vector 17 | { 18 | pub fn new() -> Self { 19 | Self { 20 | _out: Vec::new(), 21 | _marker: std::marker::PhantomData, 22 | } 23 | } 24 | } 25 | 26 | impl PredictionTransform for OctahedronReflectionTransform 27 | where 28 | Data: Vector, 29 | Data::Component: DataValue 30 | { 31 | const ID: usize = 4; 32 | 33 | type Data = Data; 34 | type Correction = NdVector<2,f64>; 35 | type Metadata = (); 36 | 37 | fn map(_orig: Self::Data, _pred: Self::Data, _: Self::Metadata) -> Self::Correction { 38 | unimplemented!() 39 | } 40 | 41 | fn map_with_tentative_metadata(&mut self, mut orig: Self::Data, mut pred: Self::Data) { 42 | // Safety: 43 | // We made sure that the data is three dimensional. 44 | debug_assert!( 45 | Data::NUM_COMPONENTS == 3, 46 | ); 47 | 48 | unsafe { 49 | if *pred.get_unchecked(2) < Data::Component::zero() { 50 | let minus_one = Data::Component::from_f64(-1.0); 51 | *pred.get_unchecked_mut(2) *= minus_one; 52 | *orig.get_unchecked_mut(2) *= minus_one; 53 | } 54 | }; 55 | 56 | let orig = unsafe{ octahedral_transform(orig) }; 57 | let pred = unsafe { octahedral_transform(pred) }; 58 | self._out.push( orig - pred ); 59 | } 60 | 61 | fn inverse(&mut self, mut pred: Self::Data, crr: Self::Correction, _: Self::Metadata) -> Self::Data { 62 | // Safety: 63 | // We made sure that the data is three dimensional. 64 | debug_assert!( 65 | Data::NUM_COMPONENTS == 3, 66 | ); 67 | 68 | let pred_lies_in_upper_half = unsafe { 69 | *pred.get_unchecked(2) > Data::Component::zero() 70 | }; 71 | 72 | if pred_lies_in_upper_half { 73 | let minus_one = Data::Component::from_f64(-1.0); 74 | unsafe{ *pred.get_unchecked_mut(2) *= minus_one; } 75 | } 76 | 77 | let pred_in_oct = unsafe { 78 | octahedral_transform(pred) 79 | }; 80 | 81 | let orig = pred_in_oct + crr; 82 | unsafe{ 83 | if *pred.get_unchecked(2) < Data::Component::zero() { 84 | let minus_one = Data::Component::from_f64(-1.0); 85 | *pred.get_unchecked_mut(2) *= minus_one; 86 | } 87 | } 88 | 89 | // Safety: 90 | // We made sure that the data is three dimensional. 91 | unsafe { 92 | octahedral_inverse_transform(orig) 93 | } 94 | } 95 | 96 | fn squeeze(&mut self) -> (FinalMetadata, Vec) { 97 | ( 98 | FinalMetadata::Global(()), 99 | std::mem::take(&mut self._out) 100 | ) 101 | } 102 | } -------------------------------------------------------------------------------- /draco-oxide/src/shared/connectivity/edgebreaker/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::{bit_coder::ReaderErr}, prelude::{ByteReader, ByteWriter}}; 2 | 3 | pub mod symbol_encoder; 4 | pub mod prediction; 5 | 6 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 7 | pub(crate) struct TopologySplit { 8 | pub merging_symbol_idx: usize, 9 | pub split_symbol_idx: usize, 10 | pub merging_edge_orientation: Orientation, 11 | } 12 | 13 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 14 | pub(crate) enum Orientation { 15 | Left, 16 | Right, 17 | } 18 | 19 | 20 | #[derive(Clone, Copy, Debug, Eq, PartialEq)] 21 | #[allow(dead_code)] // This enum is not used yet, as we only support the default configuration. 22 | pub(crate) enum EdgebreakerKind { 23 | Standard, 24 | Predictive, 25 | Valence, 26 | } 27 | 28 | impl EdgebreakerKind { 29 | #[allow(unused)] // TODO: Remove this function when the decoder is complete 30 | pub(crate) fn read_from(reader: &mut R) -> Result 31 | where R: ByteReader 32 | { 33 | let traversal_type = reader.read_u8()?; 34 | match traversal_type { 35 | 0 => Ok(Self::Standard), 36 | 1 => Ok(Self::Predictive), 37 | 2 => Ok(Self::Valence), 38 | _ => Err(Err::InvalidTraversalType(traversal_type)), 39 | } 40 | } 41 | 42 | 43 | pub(crate) fn write_to(self, writer: &mut W) 44 | where W: ByteWriter 45 | { 46 | let traversal_type = match self { 47 | Self::Standard => 0, 48 | Self::Predictive => 1, 49 | Self::Valence => 2, 50 | }; 51 | writer.write_u8(traversal_type); 52 | } 53 | } 54 | 55 | 56 | pub(crate) const MAX_VALENCE: usize = 7; 57 | pub(crate) const MIN_VALENCE: usize = 2; 58 | 59 | #[derive(Clone, Debug, Eq, PartialEq)] 60 | pub(crate) enum TraversalType { 61 | DepthFirst, 62 | #[allow(dead_code)] // This variant is not used yet. We might not implement this and may simply remove it in the future. 63 | PredictionDegree, 64 | } 65 | 66 | impl TraversalType { 67 | #[allow(unused)] // TODO: Remove this function when the decoder is complete 68 | pub(crate) fn read_from(reader: &mut R) -> Result 69 | where R: ByteReader 70 | { 71 | let traversal_type = reader.read_u8()?; 72 | match traversal_type { 73 | 0 => Ok(Self::DepthFirst), 74 | 1 => Ok(Self::PredictionDegree), 75 | _ => Err(Err::InvalidTraversalType(traversal_type)), 76 | } 77 | } 78 | 79 | pub(crate) fn write_to(self, writer: &mut W) 80 | where W: ByteWriter 81 | { 82 | let traversal_type = match self { 83 | Self::DepthFirst => 0, 84 | Self::PredictionDegree => 1, 85 | }; 86 | writer.write_u8(traversal_type); 87 | } 88 | } 89 | 90 | #[derive(Debug, thiserror::Error, PartialEq)] 91 | pub enum Err { 92 | #[error("Invalid traversal type: {0}")] 93 | InvalidTraversalType(u8), 94 | #[error("Reader error")] 95 | ReaderError(#[from] ReaderErr), 96 | } 97 | 98 | 99 | #[allow(unused)] // This enum is not used yet, as we only support the default configuration. 100 | pub(crate) enum SymbolRansEncodingConfig { 101 | LengthCoded, 102 | DirectCoded, 103 | } 104 | 105 | impl SymbolRansEncodingConfig { 106 | #[allow(unused)] // This function is not used yet, as we only support the default configuration. 107 | pub(crate) fn read_from(reader: &mut R) -> Result 108 | where R: ByteReader 109 | { 110 | let config = reader.read_u8()?; 111 | match config { 112 | 0 => Ok(Self::LengthCoded), 113 | 1 => Ok(Self::DirectCoded), 114 | _ => Err(Err::InvalidTraversalType(config)), 115 | } 116 | } 117 | 118 | #[allow(unused)] // TODO: Remove this. 119 | pub(crate) fn write_to(self, writer: &mut W) 120 | where W: ByteWriter 121 | { 122 | let config = match self { 123 | Self::LengthCoded => 0, 124 | Self::DirectCoded => 1, 125 | }; 126 | writer.write_u8(config); 127 | } 128 | } 129 | -------------------------------------------------------------------------------- /draco-oxide/src/shared/connectivity/edgebreaker/prediction.rs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/src/shared/connectivity/edgebreaker/prediction.rs -------------------------------------------------------------------------------- /draco-oxide/src/shared/connectivity/edgebreaker/symbol_encoder.rs: -------------------------------------------------------------------------------- 1 | use crate::core::bit_coder::BitReader; 2 | use crate::encode::connectivity::edgebreaker::Err; 3 | use crate::prelude::ByteReader; 4 | 5 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 6 | pub(crate) enum Symbol { 7 | C, 8 | S, 9 | L, 10 | R, 11 | E, 12 | } 13 | 14 | impl Symbol { 15 | #[inline] 16 | /// Returns the symbol as a character together with the metadata if it is a hole or handle. 17 | #[allow(unused)] // May be used in the future for debugging or logging. 18 | pub(crate) fn as_char(&self) -> (char, Option) { 19 | match self { 20 | Symbol::C => ('C', None), 21 | Symbol::R => ('R', None), 22 | Symbol::L => ('L', None), 23 | Symbol::E => ('E', None), 24 | Symbol::S => ('S', None), 25 | } 26 | } 27 | 28 | /// Returns the symbol id of the symbol. 29 | /// This id must be compatible with the draco library. 30 | pub(crate) fn get_id(self) -> usize { 31 | match self { 32 | Symbol::C => 0, 33 | Symbol::S => 1, 34 | Symbol::L => 2, 35 | Symbol::R => 3, 36 | Symbol::E => 4, 37 | } 38 | } 39 | } 40 | 41 | pub(crate) trait SymbolEncoder { 42 | fn encode_symbol(symbol: Symbol) -> Result<(u8, u64), Err>; 43 | 44 | #[allow(dead_code)] // TODO: remove this after completing the decoder. 45 | fn decode_symbol(reader: &mut BitReader) -> Symbol where R: ByteReader; 46 | } 47 | 48 | pub(crate) struct CrLight; 49 | impl SymbolEncoder for CrLight { 50 | fn encode_symbol(symbol: Symbol) -> Result<(u8, u64), Err> { 51 | match symbol { 52 | Symbol::C => Ok((1, 0)), 53 | Symbol::S => Ok((3, 0b1)), 54 | Symbol::L => Ok((3, 0b11)), 55 | Symbol::R => Ok((3, 0b101)), 56 | Symbol::E => Ok((3, 0b111)), 57 | } 58 | } 59 | 60 | fn decode_symbol(reader: &mut BitReader) -> Symbol 61 | where R: ByteReader 62 | { 63 | if reader.read_bits(1).unwrap() == 0 { 64 | return Symbol::C; 65 | } 66 | 67 | if reader.read_bits(1).unwrap() == 0 { 68 | return Symbol::R; 69 | } 70 | 71 | return match reader.read_bits(2).unwrap() { 72 | 0b00 => Symbol::L, 73 | 0b01 => Symbol::E, 74 | 0b10 => Symbol::S, 75 | _ => panic!("Internal Error: Invalid symbol encoding"), 76 | } 77 | } 78 | } 79 | 80 | 81 | -------------------------------------------------------------------------------- /draco-oxide/src/shared/connectivity/mod.rs: -------------------------------------------------------------------------------- 1 | pub(crate) mod edgebreaker; 2 | pub(crate) mod sequential; 3 | pub(crate) mod eq; -------------------------------------------------------------------------------- /draco-oxide/src/shared/connectivity/sequential.rs: -------------------------------------------------------------------------------- 1 | const TWO_POW_21: usize = 1 << 21; 2 | 3 | #[inline] 4 | pub(crate) fn index_size_from_vertex_count(vertex_count: usize) -> Result { 5 | match vertex_count { 6 | 0..0x100 => Ok(8), 7 | 0x100..0x10000 => Ok(16), 8 | 0x10000..TWO_POW_21 => Ok(21), 9 | TWO_POW_21..0x1000000 => Ok(32), 10 | _ => Err(Err::TooManyVertices), 11 | } 12 | } 13 | 14 | 15 | #[derive(Debug)] 16 | pub enum Err { 17 | TooManyVertices 18 | } 19 | 20 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 21 | pub(crate) enum Method { 22 | #[allow(unused)] 23 | Compressed, 24 | DirectIndices 25 | } 26 | 27 | impl Method { 28 | #[allow(unused)] 29 | pub fn from_id(id: u8) -> Self { 30 | match id { 31 | 0 => Self::Compressed, 32 | 1 => Self::DirectIndices, 33 | _ => panic!("Unknown method id: {}", id), 34 | } 35 | } 36 | pub fn get_id(&self) -> u8 { 37 | match self { 38 | Self::Compressed => 0, 39 | Self::DirectIndices => 1, 40 | } 41 | } 42 | } -------------------------------------------------------------------------------- /draco-oxide/src/shared/entropy/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::bit_coder::ReaderErr, prelude::{ByteReader, ByteWriter}}; 2 | 3 | pub(crate) const L_RANS_BASE: usize = 4096; 4 | pub(crate) const DEFAULT_RANS_PRECISION: usize = 12; 5 | pub(crate) const DEFAULT_RABS_PRECISION: usize = 8; 6 | 7 | #[derive(Debug, Clone, PartialEq, Eq)] 8 | pub(crate) enum SymbolEncodingMethod { 9 | #[allow(unused)] 10 | LengthCoded, 11 | DirectCoded, 12 | } 13 | 14 | impl SymbolEncodingMethod { 15 | #[allow(unused)] 16 | pub fn read_from(reader: &mut R) -> Result 17 | where R: ByteReader 18 | { 19 | let method = reader.read_u8()?; 20 | match method { 21 | 0 => Ok(SymbolEncodingMethod::LengthCoded), 22 | 1 => Ok(SymbolEncodingMethod::DirectCoded), 23 | _ => Err(Err::InvalidSymbolEncodingMethod), 24 | } 25 | } 26 | pub fn write_to(&self, writer: &mut W) 27 | where W: ByteWriter 28 | { 29 | match self { 30 | SymbolEncodingMethod::LengthCoded => writer.write_u8(0), 31 | SymbolEncodingMethod::DirectCoded => writer.write_u8(1), 32 | } 33 | } 34 | } 35 | 36 | pub(crate) struct RansSymbol { 37 | pub freq_count: usize, 38 | pub freq_cumulative: usize, 39 | } 40 | 41 | pub(crate) fn rans_build_tables(freq_counts: &[usize]) -> Result<(Vec, Vec), Err> { 42 | let mut slot_table = Vec::with_capacity(1<(reader: &mut R) -> Result 21 | where R: ByteReader 22 | { 23 | match reader.read_u8()? { 24 | 0 => Ok(EncoderMethod::Sequential), 25 | 1 => Ok(EncoderMethod::Edgebreaker), 26 | _ => panic!("Unknown encoder method ID"), 27 | } 28 | } 29 | 30 | #[inline] 31 | pub fn write_to(self, writer: &mut W) 32 | where W: ByteWriter 33 | { 34 | match self { 35 | EncoderMethod::Sequential => writer.write_u8(0), 36 | EncoderMethod::Edgebreaker => writer.write_u8(1), 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /draco-oxide/src/utils/bit_coder.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::bit_coder::ReaderErr, prelude::{ByteReader, ByteWriter}}; 2 | 3 | #[allow(unused)] 4 | pub(crate) fn leb128_read(reader: &mut W) -> Result 5 | where W: ByteReader, 6 | { 7 | let mut result: u64 = 0; 8 | let mut shift = 0; 9 | loop { 10 | let byte = reader.read_u8()?; 11 | result |= ((byte & 0x7F) as u64) << shift; 12 | if byte & 0x80 == 0 { 13 | break; 14 | } 15 | shift += 7; 16 | } 17 | Ok(result) 18 | } 19 | 20 | pub(crate) fn leb128_write(mut value: u64, writer: &mut W) 21 | where W: ByteWriter, 22 | { 23 | loop { 24 | let byte = (value & 0x7F) as u8; 25 | value >>= 7; 26 | if value == 0 { 27 | writer.write_u8(byte); 28 | break; 29 | } else { 30 | writer.write_u8(byte | 0x80); 31 | } 32 | } 33 | } 34 | 35 | 36 | #[cfg(test)] 37 | mod tests { 38 | use super::*; 39 | 40 | #[test] 41 | fn manual_test_leb128_write_read() { 42 | let mut buffer = Vec::new(); 43 | leb128_write(300, &mut buffer); 44 | assert_eq!(buffer, vec![172, 2]); 45 | 46 | let mut reader = buffer.into_iter(); 47 | let value = leb128_read(&mut reader).unwrap(); 48 | assert_eq!(value, 300); 49 | } 50 | 51 | #[test] 52 | fn more_tests_leb128() { 53 | let testdata = vec![ 54 | 0, 1, 127,128, 255, 256, 55 | 1234567890, 0xFFFFFFFFFFFFFFFF 56 | ]; 57 | let mut buffer = Vec::new(); 58 | for &value in &testdata { 59 | leb128_write(value, &mut buffer); 60 | } 61 | let mut reader = buffer.into_iter(); 62 | for &expected in &testdata { 63 | let value = leb128_read(&mut reader).unwrap(); 64 | assert_eq!(value, expected); 65 | } 66 | assert!(reader.next().is_none(), "Reader should be empty after reading all values"); 67 | } 68 | } -------------------------------------------------------------------------------- /draco-oxide/src/utils/debug.rs: -------------------------------------------------------------------------------- 1 | #[macro_export] 2 | macro_rules! debug_write { 3 | ($msg:literal, $writer:expr) => { 4 | #[cfg(feature = "debug_format")] 5 | { 6 | for byte in $msg.as_bytes() { 7 | $writer.write_u8(*byte); 8 | } 9 | } 10 | }; 11 | } 12 | 13 | #[macro_export] 14 | macro_rules! debug_expect { 15 | ($msg:literal, $reader:expr) => { 16 | #[cfg(feature = "debug_format")] 17 | { 18 | for byte in $msg.as_bytes() { 19 | assert!( 20 | *byte == $reader.read_u8().unwrap(), 21 | "Expected {:?}, but did not match.", 22 | $msg 23 | ); 24 | } 25 | } 26 | }; 27 | } -------------------------------------------------------------------------------- /draco-oxide/src/utils/geom.rs: -------------------------------------------------------------------------------- 1 | use crate::core::shared::{ 2 | NdVector, 3 | Float, 4 | Cross, 5 | Dot, 6 | }; 7 | 8 | /// Calculates the distance from a point to a triangle in 3D space. 9 | pub fn point_to_face_distance_3d(p: NdVector<3, F>, face: [NdVector<3,F>; 3]) -> F { 10 | let x = face[1] - face[0]; 11 | let y = face[2] - face[0]; 12 | let n = x.cross(y).normalize(); 13 | let distance_to_plane = n.dot(p-face[0]).abs(); 14 | 15 | let p_onto_plane = p - n * distance_to_plane; 16 | let p_onto_plane_inside_face = 17 | (p_onto_plane - face[0]).dot(face[1] - face[0]) * (face[2] - face[0]).dot(face[1] - face[0]) > F::zero() && 18 | (p_onto_plane - face[1]).dot(face[2] - face[1]) * (face[0] - face[1]).dot(face[2] - face[1]) > F::zero() && 19 | (p_onto_plane - face[2]).dot(face[0] - face[2]) * (face[1] - face[2]).dot(face[0] - face[2]) > F::zero(); 20 | 21 | if p_onto_plane_inside_face { 22 | distance_to_plane 23 | } else { 24 | [ 25 | point_to_line_distance_3d(p, [face[0], face[1]]), 26 | point_to_line_distance_3d(p, [face[1], face[2]]), 27 | point_to_line_distance_3d(p, [face[2], face[0]]), 28 | (face[1]-face[0]).norm(), 29 | (face[2]-face[1]).norm(), 30 | (face[0]-face[2]).norm() 31 | ].into_iter().min_by(|a,b| a.partial_cmp(b).unwrap()).unwrap() 32 | } 33 | } 34 | 35 | /// Calculates the distance from a point to a line in 3D space. 36 | /// The line should be expressed as two points in 3D space. 37 | pub fn point_to_line_distance_3d(p: NdVector<3, F>, line: [NdVector<3,F>; 2]) -> F { 38 | let dir = (line[1] - line[0]).normalize(); 39 | let p_line0 = p - line[0]; 40 | let n = (p_line0 - dir * p_line0.dot(dir)).normalize(); 41 | debug_assert!(n.dot(dir).abs() < F::from_f64(1e-6)); 42 | n.dot(p_line0).abs() 43 | } -------------------------------------------------------------------------------- /draco-oxide/src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::prelude::{NdVector, Vector}; 2 | 3 | pub mod geom; 4 | pub(crate) mod debug; 5 | pub(crate) mod bit_coder; 6 | 7 | #[allow(dead_code)] // Remove this when attribute encoder supports multiple groups. 8 | pub(crate) fn splice_disjoint_indices(set_of_subseqs: Vec>>) -> Vec> { 9 | let mut spliced = set_of_subseqs.into_iter() 10 | .flatten() 11 | .collect::>(); 12 | 13 | spliced.sort_by(|a, b| a.start.cmp(&b.start)); 14 | 15 | connect_subsequence(&mut spliced); 16 | spliced 17 | } 18 | 19 | 20 | #[allow(dead_code)] // Remove this when attribute encoder supports multiple groups. 21 | pub(crate) fn merge_indices(mut set_of_subseqs: Vec>>) -> Vec> { 22 | for sub_seq in set_of_subseqs.iter_mut() { 23 | connect_subsequence(sub_seq); 24 | } 25 | 26 | let set_of_subseqs_len = set_of_subseqs.len(); 27 | let mut iters = set_of_subseqs.into_iter() 28 | .map(|v| v.into_iter()) 29 | .filter_map(|mut it| if let Some(r) = it.next() { 30 | Some((r, it)) 31 | } else { 32 | None 33 | }) 34 | .collect::>(); 35 | 36 | if iters.len() < set_of_subseqs_len { 37 | // this means that there is an empty iterator, which kills all the other iterators. 38 | return Vec::new(); 39 | } 40 | 41 | let mut merged = Vec::new(); 42 | 'outer: while let Some((idx,_)) = iters.iter() 43 | .enumerate() 44 | .max_by_key(|(_, (val,_))| val.start) 45 | 46 | { 47 | let r = iters[idx].0.clone(); 48 | 49 | // make sure that all the iterators are at the position where the range ends 50 | // at least as late as 'r' starts. 51 | for (s,it) in iters.iter_mut() { 52 | while s.end < r.start { 53 | if let Some(val) = it.next() { 54 | *s = val; 55 | } else { 56 | // this means that there is some iterator that does not contain 57 | // 'r.start', so this is the end of the function. 58 | return merged; 59 | } 60 | 61 | if s.start > r.start { 62 | // this means that 'r.start' cannot be contained in the output. 63 | // so we should continue with the outer loop. 64 | continue 'outer; 65 | } 66 | } 67 | } 68 | 69 | // Now that 'r.start' is a valid start for the merged range. 70 | debug_assert!( 71 | iters.iter_mut().all(|(s,_)| s.start <= r.start), 72 | "r={:?}, current ranges: {:?}", 73 | r, 74 | iters.iter().map(|(s,_)| s).collect::>() 75 | ); 76 | // Look for the end of the range, which must have the smallest end. 77 | let end = iters.iter_mut() 78 | .map(|(s,_)| s.end) 79 | .min() 80 | .unwrap(); 81 | 82 | merged.push(r.start..end); 83 | 84 | let (r, it) = &mut iters[idx]; 85 | if let Some(val) = it.next() { 86 | *r = val; 87 | } else { 88 | return merged; 89 | } 90 | } 91 | 92 | merged 93 | } 94 | 95 | fn connect_subsequence(seq: &mut Vec>) { 96 | let mut idx1 = 0; 97 | let mut idx2 = 1; 98 | while idx2 < seq.len() { 99 | if seq[idx1].end > seq[idx2].start { 100 | panic!("Ranges must be disjoint, but they are not: {:?}", seq); 101 | } else if seq[idx1].end == seq[idx2].start { 102 | // merge the two ranges 103 | seq[idx1].end = seq[idx2].end; 104 | seq.remove(idx2); 105 | } else { 106 | idx1 += 1; 107 | idx2 += 1; 108 | } 109 | } 110 | } 111 | 112 | #[cfg(test)] 113 | mod tests { 114 | use super::*; 115 | 116 | #[test] 117 | fn test_splice_disjoint_indices() { 118 | let set_of_subseqs = vec![ 119 | vec![0..2], 120 | vec![4..6], 121 | vec![2..4, 7..9], 122 | ]; 123 | let result = splice_disjoint_indices(set_of_subseqs); 124 | assert_eq!(result, vec![0..6, 7..9]); 125 | } 126 | 127 | #[test] 128 | fn test_merge_indices() { 129 | let set_of_subseqs = vec![ 130 | vec![0..1, 1..2, 3..5, 8..10], 131 | vec![1..3, 4..6, 8..9], 132 | vec![1..4, 7..9], 133 | vec![0..79], 134 | ]; 135 | let result = merge_indices(set_of_subseqs); 136 | assert_eq!(result, vec![1..2, 8..9]); 137 | } 138 | } 139 | 140 | #[allow(dead_code)] // Remove this when attribute encoder supports multiple groups. 141 | pub(crate) fn splice_disjoint_indeces(set_of_indeces: Vec>>) -> Vec> { 142 | let mut spliced = set_of_indeces.into_iter() 143 | .flatten() 144 | .collect::>(); 145 | 146 | spliced.sort_by(|a, b| a.start.cmp(&b.start)); 147 | 148 | // ToDo: connect the adjacent ranges 149 | spliced 150 | } 151 | 152 | pub(crate) fn to_positive_i32(val: i32) -> i32 { 153 | if val >= 0 { 154 | val << 1 155 | } else { 156 | (-(val + 1) << 1) + 1 157 | } 158 | } 159 | 160 | pub(crate) fn to_positive_i32_vec(mut vec: NdVector) -> NdVector 161 | where 162 | NdVector: Vector, 163 | { 164 | for i in 0..N { 165 | *vec.get_mut(i) = to_positive_i32(*vec.get(i)); 166 | } 167 | vec 168 | } -------------------------------------------------------------------------------- /draco-oxide/tests/compatibility.rs: -------------------------------------------------------------------------------- 1 | use draco_oxide::{encode::{self, encode}, io::obj::load_obj}; 2 | use draco_oxide::prelude::ConfigType; 3 | use std::io::Write; 4 | 5 | const FILE_NAME: &str = "cube_quads"; 6 | 7 | #[test] 8 | fn en() { 9 | let mesh = load_obj(format!("tests/data/{}.obj", FILE_NAME)).unwrap(); 10 | 11 | let mut writer = Vec::new(); 12 | encode(mesh.clone(), &mut writer, encode::Config::default()).unwrap(); 13 | 14 | let mut file = std::fs::File::create(&format!("tests/outputs/{}.drc", FILE_NAME)).unwrap(); 15 | 16 | file.write_all(&writer).unwrap(); 17 | } -------------------------------------------------------------------------------- /draco-oxide/tests/data/Duck/Duck.glb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/tests/data/Duck/Duck.glb -------------------------------------------------------------------------------- /draco-oxide/tests/data/Duck/Duck.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "asset": { 3 | "generator": "COLLADA2GLTF", 4 | "version": "2.0" 5 | }, 6 | "scene": 0, 7 | "scenes": [ 8 | { 9 | "nodes": [ 10 | 0 11 | ] 12 | } 13 | ], 14 | "nodes": [ 15 | { 16 | "children": [ 17 | 2, 18 | 1 19 | ], 20 | "matrix": [ 21 | 0.009999999776482582, 22 | 0.0, 23 | 0.0, 24 | 0.0, 25 | 0.0, 26 | 0.009999999776482582, 27 | 0.0, 28 | 0.0, 29 | 0.0, 30 | 0.0, 31 | 0.009999999776482582, 32 | 0.0, 33 | 0.0, 34 | 0.0, 35 | 0.0, 36 | 1.0 37 | ] 38 | }, 39 | { 40 | "matrix": [ 41 | -0.7289686799049377, 42 | 0.0, 43 | -0.6845470666885376, 44 | 0.0, 45 | -0.4252049028873444, 46 | 0.7836934328079224, 47 | 0.4527972936630249, 48 | 0.0, 49 | 0.5364750623703003, 50 | 0.6211478114128113, 51 | -0.571287989616394, 52 | 0.0, 53 | 400.1130065917969, 54 | 463.2640075683594, 55 | -431.0780334472656, 56 | 1.0 57 | ], 58 | "camera": 0 59 | }, 60 | { 61 | "mesh": 0 62 | } 63 | ], 64 | "cameras": [ 65 | { 66 | "perspective": { 67 | "aspectRatio": 1.5, 68 | "yfov": 0.6605925559997559, 69 | "zfar": 10000.0, 70 | "znear": 1.0 71 | }, 72 | "type": "perspective" 73 | } 74 | ], 75 | "meshes": [ 76 | { 77 | "primitives": [ 78 | { 79 | "attributes": { 80 | "NORMAL": 1, 81 | "POSITION": 2, 82 | "TEXCOORD_0": 3 83 | }, 84 | "indices": 0, 85 | "mode": 4, 86 | "material": 0 87 | } 88 | ], 89 | "name": "LOD3spShape" 90 | } 91 | ], 92 | "accessors": [ 93 | { 94 | "bufferView": 0, 95 | "byteOffset": 0, 96 | "componentType": 5123, 97 | "count": 12636, 98 | "max": [ 99 | 2398 100 | ], 101 | "min": [ 102 | 0 103 | ], 104 | "type": "SCALAR" 105 | }, 106 | { 107 | "bufferView": 1, 108 | "byteOffset": 0, 109 | "componentType": 5126, 110 | "count": 2399, 111 | "max": [ 112 | 0.9995989799499512, 113 | 0.999580979347229, 114 | 0.9984359741210938 115 | ], 116 | "min": [ 117 | -0.9990839958190918, 118 | -1.0, 119 | -0.9998319745063782 120 | ], 121 | "type": "VEC3" 122 | }, 123 | { 124 | "bufferView": 1, 125 | "byteOffset": 28788, 126 | "componentType": 5126, 127 | "count": 2399, 128 | "max": [ 129 | 96.17990112304688, 130 | 163.97000122070313, 131 | 53.92519760131836 132 | ], 133 | "min": [ 134 | -69.29850006103516, 135 | 9.929369926452637, 136 | -61.32819747924805 137 | ], 138 | "type": "VEC3" 139 | }, 140 | { 141 | "bufferView": 2, 142 | "byteOffset": 0, 143 | "componentType": 5126, 144 | "count": 2399, 145 | "max": [ 146 | 0.9833459854125976, 147 | 0.9800369739532472 148 | ], 149 | "min": [ 150 | 0.026409000158309938, 151 | 0.01996302604675293 152 | ], 153 | "type": "VEC2" 154 | } 155 | ], 156 | "materials": [ 157 | { 158 | "pbrMetallicRoughness": { 159 | "baseColorTexture": { 160 | "index": 0 161 | }, 162 | "metallicFactor": 0.0 163 | }, 164 | "emissiveFactor": [ 165 | 0.0, 166 | 0.0, 167 | 0.0 168 | ], 169 | "name": "blinn3-fx" 170 | } 171 | ], 172 | "textures": [ 173 | { 174 | "sampler": 0, 175 | "source": 0 176 | } 177 | ], 178 | "images": [ 179 | { 180 | "uri": "DuckCM.png" 181 | } 182 | ], 183 | "samplers": [ 184 | { 185 | "magFilter": 9729, 186 | "minFilter": 9986, 187 | "wrapS": 10497, 188 | "wrapT": 10497 189 | } 190 | ], 191 | "bufferViews": [ 192 | { 193 | "buffer": 0, 194 | "byteOffset": 76768, 195 | "byteLength": 25272, 196 | "target": 34963 197 | }, 198 | { 199 | "buffer": 0, 200 | "byteOffset": 0, 201 | "byteLength": 57576, 202 | "byteStride": 12, 203 | "target": 34962 204 | }, 205 | { 206 | "buffer": 0, 207 | "byteOffset": 57576, 208 | "byteLength": 19192, 209 | "byteStride": 8, 210 | "target": 34962 211 | } 212 | ], 213 | "buffers": [ 214 | { 215 | "byteLength": 102040, 216 | "uri": "Duck0.bin" 217 | } 218 | ] 219 | } 220 | -------------------------------------------------------------------------------- /draco-oxide/tests/data/Duck/Duck0.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/tests/data/Duck/Duck0.bin -------------------------------------------------------------------------------- /draco-oxide/tests/data/Duck/DuckCM.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/tests/data/Duck/DuckCM.png -------------------------------------------------------------------------------- /draco-oxide/tests/data/Duck/README.md: -------------------------------------------------------------------------------- 1 | # Duck Test Model 2 | 3 | This directory contains the Duck glTF sample model from the Khronos Group's glTF Sample Models repository. 4 | 5 | ## Source 6 | 7 | The files in this directory were copied from: 8 | https://github.com/KhronosGroup/glTF-Sample-Models/tree/main/2.0/Duck 9 | 10 | ## License Information 11 | 12 | **Copyright:** Copyright 2006 Sony Computer Entertainment Inc. 13 | 14 | **License:** SCEA Shared Source License, Version 1.0 15 | 16 | The Duck model is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, either express or implied. 17 | 18 | For full license details, see: https://web.archive.org/web/20160320123355/http://research.scea.com/scea_shared_source_license.html 19 | 20 | ## Usage 21 | 22 | This model is used for testing glTF encoding and decoding functionality in the draco-oxide library. The model includes: 23 | - Duck.glb - The binary glTF file 24 | - Duck.gltf - The text-based glTF file with external references 25 | - Duck0.bin - Binary buffer data 26 | - DuckCM.png - Texture image 27 | 28 | ## Attribution 29 | 30 | This model is part of the Khronos Group glTF Sample Models collection, which provides reference models for testing and demonstrating glTF capabilities. -------------------------------------------------------------------------------- /draco-oxide/tests/data/Triangle.gltf: -------------------------------------------------------------------------------- 1 | { 2 | "scene" : 0, 3 | "scenes" : [ 4 | { 5 | "nodes" : [ 0 ] 6 | } 7 | ], 8 | 9 | "nodes" : [ 10 | { 11 | "mesh" : 0 12 | } 13 | ], 14 | 15 | "meshes" : [ 16 | { 17 | "primitives" : [ { 18 | "attributes" : { 19 | "POSITION" : 1 20 | }, 21 | "indices" : 0 22 | } ] 23 | } 24 | ], 25 | 26 | "buffers" : [ 27 | { 28 | "uri" : "simpleTriangle.bin", 29 | "byteLength" : 44 30 | } 31 | ], 32 | "bufferViews" : [ 33 | { 34 | "buffer" : 0, 35 | "byteOffset" : 0, 36 | "byteLength" : 6, 37 | "target" : 34963 38 | }, 39 | { 40 | "buffer" : 0, 41 | "byteOffset" : 8, 42 | "byteLength" : 36, 43 | "target" : 34962 44 | } 45 | ], 46 | "accessors" : [ 47 | { 48 | "bufferView" : 0, 49 | "byteOffset" : 0, 50 | "componentType" : 5123, 51 | "count" : 3, 52 | "type" : "SCALAR", 53 | "max" : [ 2 ], 54 | "min" : [ 0 ] 55 | }, 56 | { 57 | "bufferView" : 1, 58 | "byteOffset" : 0, 59 | "componentType" : 5126, 60 | "count" : 3, 61 | "type" : "VEC3", 62 | "max" : [ 1.0, 1.0, 0.0 ], 63 | "min" : [ 0.0, 0.0, 0.0 ] 64 | } 65 | ], 66 | 67 | "asset" : { 68 | "version" : "2.0" 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /draco-oxide/tests/data/cube_quads.obj: -------------------------------------------------------------------------------- 1 | v 0.0 0.0 0.0 2 | v 0.0 0.0 1.0 3 | v 0.0 1.0 0.0 4 | v 0.0 1.0 1.0 5 | v 1.0 0.0 0.0 6 | v 1.0 0.0 1.0 7 | v 1.0 1.0 0.0 8 | v 1.0 1.0 1.0 9 | 10 | vn 0.0 0.0 1.0 11 | vn 0.0 0.0 -1.0 12 | vn 0.0 1.0 0.0 13 | vn 0.0 -1.0 0.0 14 | vn 1.0 0.0 0.0 15 | vn -1.0 0.0 0.0 16 | 17 | vt 0.0 0.0 18 | vt 0.0 1.0 19 | vt 1.0 0.0 20 | vt 1.0 1.0 21 | 22 | f 1/4/2 3/3/2 7/1/2 5/2/2 23 | f 1/2/6 2/4/6 4/3/6 3/1/6 24 | f 3/1/3 4/2/3 8/4/3 7/3/3 25 | f 5/4/5 7/3/5 8/1/5 6/2/5 26 | f 1/2/4 5/4/4 6/3/4 2/1/4 27 | f 2/2/1 6/4/1 8/3/1 4/1/1 28 | -------------------------------------------------------------------------------- /draco-oxide/tests/data/simpleTriangle.bin: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/reearth/draco-oxide/923a0aea11507ec3abad45e75cd020d5ff4e2cf4/draco-oxide/tests/data/simpleTriangle.bin -------------------------------------------------------------------------------- /draco-oxide/tests/data/tetrahedron.obj: -------------------------------------------------------------------------------- 1 | # Tetrahedron with unique texture coordinates 2 | # ------------------------------------------ 3 | 4 | # 1. Vertex positions 5 | v 1.0 1.0 1.0 6 | v -1.0 -1.0 1.0 7 | v -1.0 1.0 -1.0 8 | v 1.0 -1.0 -1.0 9 | 10 | # 2. Texture coordinates (all unique) 11 | vt 0.25 1.00 # 1 12 | vt 0.00 0.50 # 2 13 | vt 0.50 0.50 # 3 14 | vt 0.75 1.00 # 4 15 | vt 1.00 0.50 # 5 16 | vt 0.25 0.50 # 6 17 | 18 | # 3. Vertex normals 19 | vn 0.57735 0.57735 0.57735 20 | vn -0.57735 -0.57735 0.57735 21 | vn -0.57735 0.57735 -0.57735 22 | vn 0.57735 -0.57735 -0.57735 23 | 24 | # 4. Faces (v / vt / vn) — updated indices 25 | f 1/1/1 2/2/2 3/3/3 26 | f 1/1/1 4/4/4 2/2/2 27 | f 1/1/1 3/3/3 4/5/4 28 | f 2/2/2 4/6/4 3/3/3 29 | -------------------------------------------------------------------------------- /draco-oxide/tests/eval/mod.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | use draco_oxide::eval::EvalWriter; 3 | use draco_oxide::io::obj::load_obj; 4 | use draco_oxide::prelude::*; 5 | 6 | const MESH_NAME: &str = "tetrahedron"; 7 | 8 | #[test] 9 | fn test_eval() { 10 | let original_mesh = load_obj(format!("tests/data/{}.obj", MESH_NAME)).unwrap(); 11 | 12 | let mut buffer = Vec::new(); 13 | let mut writer = EvalWriter::new(&mut buffer); 14 | encode(original_mesh.clone(), &mut writer, encode::Config::default()).unwrap(); 15 | 16 | // Write the evaluation data to a separate file 17 | let json = writer.get_result(); 18 | let json = serde_json::to_string_pretty(&json).unwrap(); 19 | let eval_output_path = format!("tests/outputs/{}_eval_data.txt", MESH_NAME); 20 | let mut eval_file = std::fs::File::create(&eval_output_path) 21 | .expect("Failed to create evaluation output file"); 22 | eval_file.write_all(json.as_bytes()) 23 | .expect("Failed to write evaluation data"); 24 | 25 | // Write the encoded data to a temporary file 26 | let output_path = format!("tests/outputs/{}_eval_encoded.drc", MESH_NAME); 27 | let mut file = std::fs::File::create(&output_path) 28 | .expect("Failed to create output file"); 29 | file.write_all(&buffer) 30 | .expect("Failed to write encoded data"); 31 | 32 | } -------------------------------------------------------------------------------- /draco-oxide/tests/integrated_tests.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "evaluation")] 2 | pub mod eval; 3 | 4 | pub mod compatibility; -------------------------------------------------------------------------------- /rust-toolchain.toml: -------------------------------------------------------------------------------- 1 | [toolchain] 2 | channel = "stable" 3 | components = ["rustc", "cargo", "clippy", "rustfmt", "rust-src"] 4 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | edition = "2021" 2 | reorder_imports = true 3 | -------------------------------------------------------------------------------- /util/analyze_gltf_files.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import subprocess 6 | import argparse 7 | from pathlib import Path 8 | 9 | def find_gltf_files(directory, recursive=False): 10 | """Find all .gltf and .glb files in the given directory.""" 11 | extensions = ['.gltf', '.glb'] 12 | files = [] 13 | 14 | if recursive: 15 | for ext in extensions: 16 | files.extend(Path(directory).rglob(f'*{ext}')) 17 | else: 18 | for ext in extensions: 19 | files.extend(Path(directory).glob(f'*{ext}')) 20 | 21 | return sorted(files) 22 | 23 | def run_analyzer(file_path): 24 | """Run the analyzer on a single file.""" 25 | cmd = ['cargo', 'run', '--release', '--bin', 'analyzer', '--', '--original', str(file_path)] 26 | 27 | print(f"\nAnalyzing: {file_path}") 28 | print(f"Command: {' '.join(cmd)}") 29 | 30 | try: 31 | result = subprocess.run(cmd, capture_output=True, text=True) 32 | 33 | if result.returncode == 0: 34 | print(f"✓ Successfully analyzed {file_path}") 35 | if result.stdout: 36 | print("Output:", result.stdout[:200] + "..." if len(result.stdout) > 200 else result.stdout) 37 | else: 38 | print(f"✗ Failed to analyze {file_path}") 39 | if result.stderr: 40 | print("Error:", result.stderr) 41 | except Exception as e: 42 | print(f"✗ Exception while analyzing {file_path}: {e}") 43 | 44 | def main(): 45 | parser = argparse.ArgumentParser(description='Analyze all glTF/GLB files in a directory using the draco-rs analyzer') 46 | parser.add_argument('directory', help='Directory containing glTF/GLB files') 47 | parser.add_argument('-r', '--recursive', action='store_true', help='Search for files recursively') 48 | 49 | args = parser.parse_args() 50 | 51 | if not os.path.isdir(args.directory): 52 | print(f"Error: '{args.directory}' is not a valid directory") 53 | sys.exit(1) 54 | 55 | files = find_gltf_files(args.directory, args.recursive) 56 | 57 | if not files: 58 | print(f"No glTF/GLB files found in '{args.directory}'") 59 | if not args.recursive: 60 | print("Tip: Use -r flag to search recursively") 61 | sys.exit(0) 62 | 63 | print(f"Found {len(files)} glTF/GLB file(s)") 64 | 65 | for file in files: 66 | run_analyzer(file) 67 | 68 | print(f"\nCompleted analyzing {len(files)} file(s)") 69 | 70 | if __name__ == '__main__': 71 | main() -------------------------------------------------------------------------------- /util/extract_glb_json.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | """ 3 | Extract JSON part from GLB files and save as separate JSON files. 4 | 5 | GLB (GL Transmission Format Binary) files contain: 6 | - 12-byte header 7 | - JSON chunk (with chunk header) 8 | - Binary chunk (optional, with chunk header) 9 | 10 | This script extracts the JSON chunk and saves it with the same filename but .json extension. 11 | """ 12 | 13 | import struct 14 | import json 15 | import sys 16 | import os 17 | from pathlib import Path 18 | 19 | 20 | def extract_json_from_glb(glb_path): 21 | """Extract JSON chunk from GLB file and return as dictionary.""" 22 | with open(glb_path, 'rb') as f: 23 | # Read GLB header (12 bytes) 24 | magic = f.read(4) 25 | if magic != b'glTF': 26 | raise ValueError(f"Not a valid GLB file: {glb_path}") 27 | 28 | version = struct.unpack(' [glb_file2] ...") 88 | print(" or: python extract_glb_json.py *.glb") 89 | sys.exit(1) 90 | 91 | success_count = 0 92 | total_count = len(sys.argv) - 1 93 | 94 | for glb_file in sys.argv[1:]: 95 | if process_glb_file(glb_file): 96 | success_count += 1 97 | 98 | print(f"\nProcessed {success_count}/{total_count} files successfully.") 99 | 100 | 101 | if __name__ == "__main__": 102 | main() --------------------------------------------------------------------------------