# draco-io
[](https://crates.io/crates/draco-io)
[](https://docs.rs/draco-io)
[](https://github.com/Filyus/draco-rust/actions/workflows/ci.yml)
[](https://github.com/Filyus/draco-rust/blob/main/LICENSE)
A Rust library for reading and writing 3D mesh file formats with Draco compression support. Built on top of `draco-core`, this crate provides a unified API for working with OBJ, PLY, FBX, and glTF/GLB formats.
For the project overview, compatibility notes, and benchmarks, see the
[Draco Rust workspace README](https://github.com/Filyus/draco-rust).
## Features
- **Unified API**: Common `Reader` and `Writer` traits across all formats
- **In-Memory I/O**: Common `ReadFromBytes` and `WriteToBytes` traits
- **Draco Compression**: Full support for `KHR_draco_mesh_compression` in glTF/GLB
- **Document-Preserving Compression**: Draco-compress an existing glTF/GLB in
place, keeping materials, textures, animations, and other content (`compress_gltf_bytes`)
- **Multiple Formats**: OBJ, PLY, FBX (ASCII/Binary), glTF (JSON/Binary/Embedded)
- **Scene Graph Support**: Read and write scene hierarchies with transforms
- **Point Cloud Support**: Read/write point clouds (OBJ, PLY)
- **Feature Flags**: Per-format reader and writer features
## Supported Formats
| OBJ | ✓ | ✓ | - | Positions, normals, texcoords, named groups, point clouds |
| PLY | ✓ | ✓ | - | ASCII/binary, normals, colors, per-vertex texcoords |
| FBX | ✓ | ✓ | - | Binary 7.x, positions and triangle faces only |
| glTF | ✓ | ✓ | ✓ | JSON + separate .bin |
| GLB | ✓ | ✓ | ✓ | Binary container |
## Geometry Contract
`draco-io` is a geometry bridge for the `draco-core` data model, not a
general-purpose asset importer. The stable contract is:
- Meshes use triangle faces; readers triangulate polygon faces when the source
format provides polygons, and reject unsupported primitive modes explicitly.
- `Position` is the required mesh attribute. `Normal`, `Color`, `TexCoord`, and
`Generic` are preserved when the format can represent them as Draco
attributes.
- Scene support is intentionally small: names, hierarchy, transforms, and mesh
parts. Materials, textures, cameras, lights, animation, skinning, and
arbitrary format extras are out of scope for now.
- Writers should not silently claim to preserve attributes that the target
format cannot encode. FBX writing currently accepts position-only meshes.
## Installation
Add to your `Cargo.toml`:
```toml
[dependencies]
draco-io = "0.1"
```
### Feature Flags
| `all-readers` | ✓ | Reading support for all formats |
| `all-writers` | ✓ | Writing support for all formats |
| `obj-reader` | ✓ | OBJ reading support |
| `obj-writer` | ✓ | OBJ writing support |
| `ply-reader` | ✓ | PLY reading support |
| `ply-writer` | ✓ | PLY writing support |
| `fbx-reader` | ✓ | FBX reading support |
| `fbx-writer` | ✓ | FBX writing support |
| `gltf-reader` | ✓ | glTF/GLB reading support |
| `gltf-writer` | ✓ | glTF/GLB writing support |
| `scene` | ✓ | Scene graph API for hierarchical formats |
| `compression` | ✓ | zlib compression for FBX |
To use only one format direction (smaller binary):
```toml
[dependencies]
draco-io = { version = "0.1", default-features = false, features = ["gltf-reader"] }
```
## Quick Start
### Reading a Mesh
```rust
use draco_io::{Reader, ObjReader};
use std::io;
fn load_mesh(path: &str) -> io::Result<draco_core::mesh::Mesh> {
let mut reader = ObjReader::open(path)?;
reader.read_mesh()
}
```
### Writing a Mesh
```rust
use draco_io::{Writer, ObjWriter};
use draco_core::mesh::Mesh;
use std::io;
fn save_mesh(mesh: &Mesh, path: &str) -> io::Result<()> {
let mut writer = ObjWriter::new();
writer.add_mesh(mesh, Some("MyMesh"))?;
writer.write(path)
}
```
### Generic Functions (Polymorphism)
Write format-agnostic code using the trait interface:
```rust
use draco_io::{Reader, Writer};
use draco_core::mesh::Mesh;
use std::io;
// Works with any reader implementation
fn load<R: Reader>(path: &str) -> io::Result<Mesh> {
let mut reader = R::open(path)?;
reader.read_mesh()
}
// Works with any writer implementation
fn save<W: Writer>(mut writer: W, mesh: &Mesh, path: &str) -> io::Result<()> {
writer.add_mesh(mesh, Some("Model"))?;
println!("Vertices: {}, Faces: {}", writer.vertex_count(), writer.face_count());
writer.write(path)
}
```
### GLB with Draco Compression
```rust
use draco_io::gltf_writer::GltfWriter;
use draco_core::mesh::Mesh;
fn write_compressed_glb(mesh: &Mesh, path: &str) -> Result<(), draco_io::GltfWriteError> {
let mut writer = GltfWriter::new();
// Add mesh with Draco compression (uses default quantization)
writer.add_draco_mesh(mesh, Some("CompressedMesh"), None)?;
// Write as binary GLB
writer.write_glb(path)
}
```
### Reading Draco-Compressed glTF
```rust
use draco_io::gltf_reader::GltfReader;
fn read_draco_glb(path: &str) -> Result<(), draco_io::GltfError> {
let reader = GltfReader::open(path)?;
// Decode all Draco-compressed meshes
for (info, mesh) in reader.decode_all_draco_meshes()? {
println!("Mesh '{}': {} faces, {} points",
info.mesh_name.as_deref().unwrap_or("unnamed"),
mesh.num_faces(),
mesh.num_points());
}
Ok(())
}
```
## Unified Trait API
All readers and writers implement common traits for a consistent interface:
### Writer Trait
```rust
pub trait Writer: Sized {
fn new() -> Self;
fn add_mesh(&mut self, mesh: &Mesh, name: Option<&str>) -> io::Result<()>;
fn write<P: AsRef<Path>>(&self, path: P) -> io::Result<()>;
fn vertex_count(&self) -> usize;
fn face_count(&self) -> usize;
}
```
### Reader Trait
```rust
pub trait Reader: Sized {
fn open<P: AsRef<Path>>(path: P) -> io::Result<Self>;
fn read_mesh(&mut self) -> io::Result<Mesh>;
fn read_meshes(&mut self) -> io::Result<Vec<Mesh>>;
}
```
### In-Memory I/O Traits
```rust
pub trait ReadFromBytes: Sized {
fn from_bytes(bytes: &[u8]) -> io::Result<Self>;
}
pub trait WriteToBytes: Writer {
fn write_to_vec(&self) -> io::Result<Vec<u8>>;
fn write_to<W: io::Write>(&self, writer: &mut W) -> io::Result<()>;
}
```
### Scene Traits
For scene graph support with transforms and hierarchies. Scene APIs are in
`draco_io::scene` and re-exported from `draco_io` when the `scene` feature is
enabled. glTF and FBX readers expose native scene graphs; flat OBJ/PLY readers
can be wrapped explicitly with `flatten_to_scene`.
```rust
pub trait SceneReader: Reader {
fn read_scene(&mut self) -> io::Result<Scene>;
}
pub trait SceneWriter: Writer {
fn add_scene(&mut self, scene: &Scene) -> io::Result<()>;
}
```
## Format-Specific Features
### OBJ Writer
```rust
use draco_io::{ObjWriter, Writer, PointCloudWriter};
let mut obj = ObjWriter::new();
// Named object groups
obj.add_mesh(&mesh, Some("Cube"))?;
// Point clouds
obj.add_points(&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]]);
obj.write("output.obj")?;
```
### PLY Writer
```rust
use draco_io::PlyWriter;
let mut ply = PlyWriter::new();
ply.set_binary_little_endian(true); // optional, ASCII is the default
// Add mesh
ply.add_mesh(&mesh, None)?;
// Or point cloud with colors
ply.add_points_with_colors(
&[[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]],
&[[255, 0, 0, 255], [0, 255, 0, 255]],
);
ply.write("output.ply")?;
```
### FBX Writer
```rust
use draco_io::FbxWriter;
let mut fbx = FbxWriter::new()
.with_compression(true) // Enable zlib output (default feature)
.with_compression_threshold(1000); // Min size to compress
fbx.add_mesh(&mesh, Some("Model"))?;
fbx.write("output.fbx")?;
```
### glTF Writer
```rust
use draco_io::gltf_writer::{GltfWriter, QuantizationBits};
let mut gltf = GltfWriter::new();
// Custom quantization settings
let quant = QuantizationBits {
position: 14,
normal: 10,
color: 8,
texcoord: 12,
generic: 8,
};
gltf.add_draco_mesh(&mesh, Some("HighQuality"), Some(quant))?;
// Multiple output formats:
gltf.write_glb("output.glb")?; // Binary GLB (single file)
gltf.write_gltf("out.gltf", "out.bin")?; // JSON + separate binary
gltf.write_gltf_embedded("embedded.gltf")?; // Pure text with base64
```
### glTF Reader with Scene Graph
```rust
use draco_io::{GltfReader, SceneReader};
let mut reader = GltfReader::open("scene.glb")?;
let scene = reader.read_scene()?;
// Traverse scene hierarchy
for node in &scene.root_nodes {
println!("Node: {:?}", node.name);
for part in &node.parts {
println!(" Mesh: {:?}, {} faces",
part.name,
part.mesh.num_faces());
}
}
```
## Examples
The crate includes several examples:
```bash
# Run unified API demo
cargo run --example unified_api
# Run polymorphic usage demo
cargo run --example polymorphic
# Run glTF demo
cargo run --example gltf_demo
# Run FBX demo
cargo run --example fbx_demo
```
## Module Structure
```
draco-io
├── Traits
│ ├── traits::Writer - Common writer interface
│ ├── traits::Reader - Common reader interface
│ ├── traits::WriteToBytes - In-memory writer output
│ ├── traits::ReadFromBytes - In-memory reader input
│ ├── traits::PointCloudWriter - Point cloud writing
│ └── traits::PointCloudReader - Point cloud reading
│
├── Scene (feature = "scene")
│ ├── scene::SceneWriter - Scene graph writing
│ ├── scene::SceneReader - Scene graph reading
│ ├── scene::Scene - Scene container
│ └── scene::flatten_to_scene - Flat mesh adapter
│
├── Readers (feature = "all-readers")
│ ├── obj_reader - Wavefront OBJ
│ ├── ply_reader - Stanford PLY
│ ├── fbx_reader - Autodesk FBX
│ └── gltf_reader - glTF/GLB with Draco support
│
├── Writers (feature = "all-writers")
│ ├── obj_writer - Wavefront OBJ
│ ├── ply_writer - Stanford PLY (ASCII/binary)
│ ├── fbx_writer - Autodesk binary FBX
│ └── gltf_writer - glTF/GLB with Draco compression
│
└── glTF geometry + Draco compression
├── gltf_geometry - Reader-agnostic geometry decode + shared glTF error type
│ (compiled with gltf-reader OR gltf-writer)
└── gltf_compress - Document-preserving Draco compression. The in-memory
compress_gltf_value needs only gltf-writer; the byte API
compress_gltf_bytes also needs gltf-reader
```
The `gltf_geometry` split lets callers that already have a parsed glTF document
(for example via [`draco-gltf`](https://crates.io/crates/draco-gltf) on gltf-rs)
reuse the same geometry decoder and drive `compress_gltf_value` with only the
writer — never linking the glTF reader.
## Dependencies
- `draco-core` - Core compression/decompression
- `thiserror` - Error handling
- `byteorder` - Binary I/O
- `serde`, `serde_json` - glTF JSON parsing
- `miniz_oxide` (optional) - zlib compression for FBX
## License
Apache-2.0 (same as the original Draco library)
## See Also
- [draco-core](../draco-core) - Core compression library
- [API Reference](API.md) - Detailed API documentation
- [Google Draco](https://github.com/google/draco) - Original C++ implementation