draco-io 0.1.0

Rust IO helpers for Draco geometry compression formats
Documentation

draco-io

Crates.io Docs.rs Rust CI 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.

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

Format Read Write Draco Compression Notes
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:

[dependencies]
draco-io = "0.1"

Feature Flags

Feature Default Description
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):

[dependencies]
draco-io = { version = "0.1", default-features = false, features = ["gltf-reader"] }

Quick Start

Reading a Mesh

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

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:

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

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

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

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

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

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.

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

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

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

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

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

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:

# 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 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