draco_io/lib.rs
1//! Format I/O layer for Draco geometry.
2//!
3//! `draco-io` maps external 3D formats onto the geometry types from
4//! `draco-core`. It handles file/container concerns such as OBJ, PLY, FBX,
5//! glTF, GLB, scene hierarchy, and `KHR_draco_mesh_compression`; raw `.drc`
6//! bitstream encoding and decoding stays in `draco-core`.
7//!
8//! # Supported Formats
9//!
10//! | Format | Read | Write | Draco compression |
11//! |--------|------|-------|-------------------|
12//! | OBJ | yes | yes | no |
13//! | PLY | yes | yes | no |
14//! | FBX | yes | yes | no |
15//! | glTF | yes | yes | yes |
16//! | GLB | yes | yes | yes |
17//!
18//! # Feature Model
19//!
20//! The default feature set enables all readers, all writers, optional
21//! compression support, and point-cloud Draco decoding. For smaller builds, use
22//! `default-features = false` and enable only the format features needed, such
23//! as `gltf-reader`, `gltf-writer`, `obj-reader`, or `ply-writer`.
24//!
25//! # Geometry Contract
26//!
27//! `draco-io` maps source formats onto the Draco geometry model. Meshes use
28//! triangle faces, `Position` is the required attribute, and `Normal`, `Color`,
29//! `TexCoord`, and `Generic` are preserved when the file format can represent
30//! them as Draco attributes. Scene support in the *geometry model* is limited to
31//! names, hierarchy, transforms, and mesh parts; materials, textures, cameras,
32//! lights, animation, skinning, structural metadata, and arbitrary format extras
33//! are not represented in that model.
34//!
35//! # Document-preserving glTF compression
36//!
37//! Decoding into the geometry model and re-emitting a fresh glTF necessarily
38//! drops anything the model does not represent (materials, textures, and so on).
39//! When the goal is to Draco-compress an existing glTF/GLB **in place**, use
40//! [`compress_gltf_bytes`]: it rewrites only the compressible mesh geometry and
41//! carries the rest of the document through untouched — materials, textures,
42//! images, samplers, cameras, nodes, animations, skins, `extras`, and unknown
43//! JSON extensions survive. Unknown extension content that appears to contain
44//! binary buffer/view/offset references is rejected because it cannot be
45//! remapped safely. Default attribute quantization is lossy. Embedding glTF
46//! buffers does not embed external image URIs.
47//!
48//! # Unified Trait API
49//!
50//! All readers implement [`Reader`] and all writers implement [`Writer`]:
51//!
52//! ```no_run
53//! # #[cfg(all(feature = "obj-reader", feature = "obj-writer", feature = "ply-writer"))]
54//! # fn main() -> Result<(), std::io::Error> {
55//! use std::io;
56//! use draco_core::mesh::Mesh;
57//! use draco_io::{ObjReader, ObjWriter, PlyWriter, Reader, Writer};
58//!
59//! // Generic read function
60//! fn load<R: Reader>(path: &str) -> io::Result<Mesh> {
61//! let mut reader = R::open(path)?;
62//! reader.read_mesh()
63//! }
64//!
65//! // Generic write function
66//! fn save<W: Writer>(mut writer: W, mesh: &Mesh) -> io::Result<()> {
67//! writer.add_mesh(mesh, Some("Model"))?;
68//! writer.write("output.ext")
69//! }
70//!
71//! // Works with any format
72//! let mesh = load::<ObjReader>("input.obj")?;
73//! save(ObjWriter::new(), &mesh)?;
74//! save(PlyWriter::new(), &mesh)?;
75//! # Ok(())
76//! # }
77//! # #[cfg(not(all(feature = "obj-reader", feature = "obj-writer", feature = "ply-writer")))]
78//! # fn main() {}
79//! ```
80//!
81//! # Format-Specific Features
82//!
83//! While the trait provides a common interface, each writer has format-specific methods:
84//!
85//! ```no_run
86//! # #[cfg(all(feature = "fbx-writer", feature = "gltf-writer", feature = "obj-writer", feature = "ply-writer"))]
87//! # fn main() -> Result<(), Box<dyn std::error::Error>> {
88//! use draco_io::{FbxWriter, GltfWriter, ObjWriter, PlyWriter};
89//! use draco_io::{PointCloudWriter, Writer};
90//! # let mesh = draco_core::mesh::Mesh::new();
91//! # let points = [[0.0, 0.0, 0.0]];
92//! # let colors = [[255, 255, 255, 255]];
93//!
94//! // OBJ: Named groups
95//! let mut obj = ObjWriter::new();
96//! obj.add_mesh(&mesh, Some("Cube"))?;
97//!
98//! // PLY: Point clouds with colors
99//! let mut ply = PlyWriter::new();
100//! ply.add_points_with_colors(&points, &colors);
101//!
102//! // FBX: Optional compression
103//! let mut fbx = FbxWriter::new().with_compression(true);
104//! fbx.add_mesh(&mesh, Some("Model"))?;
105//!
106//! // glTF: Custom quantization, multiple output formats
107//! let mut gltf = GltfWriter::new();
108//! gltf.add_draco_mesh(&mesh, Some("Model"), None)?; // Use default quantization
109//! gltf.write_glb("output.glb")?; // Binary GLB
110//! gltf.write_gltf("out.gltf", "out.bin")?; // Separate files
111//! gltf.write_gltf_embedded("embedded.gltf")?; // Pure text
112//! # Ok(())
113//! # }
114//! # #[cfg(not(all(feature = "fbx-writer", feature = "gltf-writer", feature = "obj-writer", feature = "ply-writer")))]
115//! # fn main() {}
116//! ```
117//!
118//! # glTF/GLB with Draco Compression
119//!
120//! The `gltf_reader` and `gltf_writer` modules provide focused support for
121//! Draco triangle meshes through `KHR_draco_mesh_compression`. They validate
122//! the container enough to avoid silently dropping required glTF features; they
123//! are not a full glTF SDK.
124//!
125//! Three output formats are available:
126//!
127//! - **GLB**: Binary container (single .glb file)
128//! - **glTF + .bin**: JSON with separate binary file
129//! - **glTF (embedded)**: Pure text JSON with base64 data URIs
130//!
131//! ## Reading Draco-compressed glTF
132//!
133//! ```no_run
134//! # #[cfg(feature = "gltf-reader")]
135//! # fn main() -> Result<(), draco_io::GltfError> {
136//! use draco_io::gltf_reader::GltfReader;
137//!
138//! let reader = GltfReader::open("model.glb")?;
139//! for (info, mesh) in reader.decode_all_draco_meshes()? {
140//! println!("Mesh '{}' has {} faces",
141//! info.mesh_name.unwrap_or_default(),
142//! mesh.num_faces());
143//! }
144//! # Ok(())
145//! # }
146//! # #[cfg(not(feature = "gltf-reader"))]
147//! # fn main() {}
148//! ```
149//!
150//! ## Writing Draco-compressed GLB
151//!
152//! ```no_run
153//! # #[cfg(feature = "gltf-writer")]
154//! # fn main() -> Result<(), draco_io::GltfWriteError> {
155//! use draco_io::gltf_writer::GltfWriter;
156//! # let mesh = draco_core::mesh::Mesh::new();
157//!
158//! let mut writer = GltfWriter::new();
159//! writer.add_draco_mesh(&mesh, Some("MyMesh"), None)?; // Use default quantization
160//!
161//! // Option 1: Binary GLB (most compact)
162//! writer.write_glb("output.glb")?;
163//!
164//! // Option 2: Separate JSON and binary
165//! writer.write_gltf("output.gltf", "output.bin")?;
166//!
167//! // Option 3: Pure text with embedded data (no external files)
168//! writer.write_gltf_embedded("output.gltf")?;
169//! # Ok(())
170//! # }
171//! # #[cfg(not(feature = "gltf-writer"))]
172//! # fn main() {}
173//! ```
174
175#![cfg_attr(docsrs, feature(doc_cfg))]
176
177// Reader modules.
178#[cfg(feature = "fbx-reader")]
179#[cfg_attr(docsrs, doc(cfg(feature = "fbx-reader")))]
180pub mod fbx_reader;
181// Reader-agnostic glTF geometry decode + shared error type. Available with the
182// reader or the writer, so the compressor reuses it without the reader.
183#[cfg(any(feature = "gltf-reader", feature = "gltf-writer"))]
184#[cfg_attr(
185 docsrs,
186 doc(cfg(any(feature = "gltf-reader", feature = "gltf-writer")))
187)]
188pub mod gltf_container;
189#[cfg(any(feature = "gltf-reader", feature = "gltf-writer"))]
190#[cfg_attr(
191 docsrs,
192 doc(cfg(any(feature = "gltf-reader", feature = "gltf-writer")))
193)]
194pub mod gltf_geometry;
195#[cfg(any(feature = "gltf-reader", feature = "gltf-writer"))]
196#[cfg_attr(
197 docsrs,
198 doc(cfg(any(feature = "gltf-reader", feature = "gltf-writer")))
199)]
200pub mod gltf_khr_draco;
201#[cfg(feature = "gltf-reader")]
202#[cfg_attr(docsrs, doc(cfg(feature = "gltf-reader")))]
203pub mod gltf_reader;
204#[cfg(feature = "obj-reader")]
205#[cfg_attr(docsrs, doc(cfg(feature = "obj-reader")))]
206pub mod obj_reader;
207#[cfg(feature = "ply-reader")]
208#[cfg_attr(docsrs, doc(cfg(feature = "ply-reader")))]
209pub mod ply_reader;
210
211// Writer modules.
212#[cfg(feature = "fbx-writer")]
213#[cfg_attr(docsrs, doc(cfg(feature = "fbx-writer")))]
214pub mod fbx_writer;
215#[cfg(feature = "gltf-writer")]
216#[cfg_attr(docsrs, doc(cfg(feature = "gltf-writer")))]
217/// Document-preserving glTF Draco compression (keeps materials, textures, etc.).
218///
219/// The in-memory [`gltf_compress::compress_gltf_value`] needs only the writer;
220/// the byte API ([`gltf_compress::compress_gltf_bytes`]) also needs the reader.
221pub mod gltf_compress;
222#[cfg(feature = "gltf-writer")]
223#[cfg_attr(docsrs, doc(cfg(feature = "gltf-writer")))]
224/// glTF/GLB writer with Draco mesh compression support.
225pub mod gltf_writer;
226#[cfg(feature = "obj-writer")]
227#[cfg_attr(docsrs, doc(cfg(feature = "obj-writer")))]
228pub mod obj_writer;
229#[cfg(feature = "ply-writer")]
230#[cfg_attr(docsrs, doc(cfg(feature = "ply-writer")))]
231pub mod ply_writer;
232
233/// Shared PLY storage-format enum.
234pub mod ply_format;
235// Traits module is always available
236pub mod traits;
237
238// Scene-graph layer (data model + traits) is only compiled for hierarchical
239// formats (glTF, FBX) that actually carry a scene.
240#[cfg(feature = "scene")]
241#[cfg_attr(docsrs, doc(cfg(feature = "scene")))]
242pub mod scene;
243
244// Re-export main types for convenience
245#[cfg(feature = "fbx-reader")]
246#[cfg_attr(docsrs, doc(cfg(feature = "fbx-reader")))]
247pub use fbx_reader::{FbxMemoryReader, FbxReader};
248#[cfg(feature = "fbx-writer")]
249#[cfg_attr(docsrs, doc(cfg(feature = "fbx-writer")))]
250pub use fbx_writer::FbxWriter;
251// Reader-agnostic geometry decode + shared error type (reader or writer).
252#[cfg(feature = "gltf-writer")]
253pub use gltf_container::{
254 build_glb_container, encode_data_uri, serialize_gltf_document, OutputFormat,
255};
256#[cfg(any(feature = "gltf-reader", feature = "gltf-writer"))]
257pub use gltf_container::{
258 decode_data_uri, parse_gltf_container, resolve_gltf_buffers, resolve_resource_uri,
259 ExternalFilePolicy, FileResourceResolver, GltfBufferReference, GltfContainer,
260 GltfContainerFormat, ResourceLimits, ResourceResolver,
261};
262#[cfg(any(feature = "gltf-reader", feature = "gltf-writer"))]
263#[cfg_attr(
264 docsrs,
265 doc(cfg(any(feature = "gltf-reader", feature = "gltf-writer")))
266)]
267pub use gltf_geometry::{decode_geometry, AccessorSource, DecodedAccessor, GltfError};
268#[cfg(any(feature = "gltf-reader", feature = "gltf-writer"))]
269pub use gltf_khr_draco::{
270 parse_khr_draco_extension_value, parse_khr_draco_mesh_compression, validate_khr_draco_document,
271 KhrDracoExtension, KhrDracoMeshCompression, KHR_DRACO_MESH_COMPRESSION,
272};
273// In-memory compressor core: writer only.
274#[cfg(feature = "gltf-writer")]
275#[cfg_attr(docsrs, doc(cfg(feature = "gltf-writer")))]
276pub use gltf_compress::{
277 compress_gltf_value, consolidate_gltf_buffers, validate_gltf_document_binary_layout,
278 validate_gltf_document_for_repacking, CompressionOutput, CompressionReport, EncodingMethod,
279 GltfCompressionOptions, PreserveReason, PreservedPrimitive, PrimitiveLocation,
280 QuantizationOptions,
281};
282// Byte compressor API: needs the reader to parse + resolve buffers.
283#[cfg(all(feature = "gltf-reader", feature = "gltf-writer"))]
284#[cfg_attr(
285 docsrs,
286 doc(cfg(all(feature = "gltf-reader", feature = "gltf-writer")))
287)]
288pub use gltf_compress::{
289 compress_gltf_bytes, compress_gltf_bytes_with_base_path, compress_gltf_bytes_with_options,
290 compress_gltf_bytes_with_resolver,
291};
292#[cfg(feature = "gltf-reader")]
293#[cfg_attr(docsrs, doc(cfg(feature = "gltf-reader")))]
294pub use gltf_reader::{
295 DracoPrimitiveInfo, GltfDocumentMetadata, GltfNodeMetadata, GltfReader, GltfSceneMetadata,
296};
297#[cfg(feature = "gltf-writer")]
298#[cfg_attr(docsrs, doc(cfg(feature = "gltf-writer")))]
299pub use gltf_writer::{GltfWriteError, GltfWriter};
300#[cfg(feature = "obj-reader")]
301#[cfg_attr(docsrs, doc(cfg(feature = "obj-reader")))]
302pub use obj_reader::ObjReader;
303#[cfg(feature = "obj-writer")]
304#[cfg_attr(docsrs, doc(cfg(feature = "obj-writer")))]
305pub use obj_writer::ObjWriter;
306pub use ply_format::PlyFormat;
307#[cfg(feature = "ply-reader")]
308#[cfg_attr(docsrs, doc(cfg(feature = "ply-reader")))]
309pub use ply_reader::PlyReader;
310#[cfg(feature = "ply-writer")]
311#[cfg_attr(docsrs, doc(cfg(feature = "ply-writer")))]
312pub use ply_writer::PlyWriter;
313#[cfg(feature = "scene")]
314#[cfg_attr(docsrs, doc(cfg(feature = "scene")))]
315pub use scene::{
316 flatten_to_scene, MeshInstance, Scene, SceneNode, SceneReader, SceneWriter, Transform,
317};
318pub use traits::{PointCloudReader, PointCloudWriter, ReadFromBytes, Reader, WriteToBytes, Writer};