#wavefront-obj #3d #parser #graphics #wavefront

wobj

Wavefront OBJ polygonal geometry and MTL material parser library

4 releases (2 breaking)

Uses new Rust 2024

0.3.0 Jan 17, 2026
0.2.1 Jan 15, 2026
0.2.0 Jan 14, 2026
0.1.0 Jan 13, 2026

#876 in Game dev

25 downloads per month
Used in bevy_obj

MIT/Apache

45KB
1K SLoC

wobj

Wavefront OBJ polygonal geometry and MTL material parser library.

  • OBJ parsing only supports polygonal geometry and ignores all other statements.

  • MTL parsing supports both the original spec and the PBR extensions.

Usage

To load an OBJ file:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let bytes = std::fs::read("assets/cube.obj")?;
    let obj = wobj::Obj::parse(&bytes)?;

    // To get the meshes
    for mesh in &obj.meshes() {
        println!("Name: {:?}", mesh.name());

        // To triangulate the meshes
        let (indicies, verticies) = mesh.triangulate()?;
    }

    Ok(())
}

To load a material:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let bytes = std::fs::read("assets/cube.mtl")?;
    let mtl = wobj::Mtl::parse(&bytes)?;
    let material: Option<&wobj::Material> = mtl.get("CubeMaterial");
    Ok(())
}

Since OBJ files can use a different MTL file for each material, if we want to load all used materials without wasting work:

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let obj_path = std::path::Path::new("assets/cube.obj");
    let parent_path = obj_path.parent().unwrap();

    // Load the OBJ
    let obj = wobj::Obj::parse(&std::fs::read(obj_path)?)?;
    // Use a HashMap to store loaded MTLs
    let mut mtls = std::collections::HashMap::new();

    // Iterate through all meshes in the OBJ
    for mesh in &obj.meshes() {
        // Check if the mesh has a material
        if let Some(mtllib) = mesh.mtllib()
            && let Some(material_name) = mesh.material()
        {
            // Load the MTL if it has not been loaded before
            if !mtls.contains_key(mtllib) {
                // MTL-lib path is relative to the OBJ file
                let mtl_path = parent_path.join(mtllib);
                let mtl = wobj::Mtl::parse(&std::fs::read(mtl_path)?)?;
                mtls.insert(mtllib, mtl);
            }

            // Get the material
            let material = mtls
                .get(mtllib)
                .and_then(|m| m.get(material_name))
                .expect("Material not found");

            // Use the material
            println!("Material '{material_name}': {material:?}");
        }
    }

    Ok(())
}

License

Licensed under either of

at your option.

Dependencies

~1.5–2MB
~40K SLoC