Crate tobj

source ·
Expand description

Tiny OBJ Loader

A tiny OBJ loader, inspired by Syoyo’s excellent tinyobjloader. Aims to be a simple and lightweight option for loading OBJ files.

Just returns two Vecs containing loaded models and materials.

Triangulation

Meshes can be triangulated on the fly or left as-is.

Only polygons that are trivially convertible to triangle fans are supported. Arbitrary polygons may not behave as expected. The best solution would be to convert your mesh to solely consist of triangles in your modeling software.

Optional – Normals & Texture Coordinates

It is assumed that all meshes will at least have positions, but normals and texture coordinates are optional.

If no normals or texture coordinates are found then the corresponding Vecs for the Mesh will be empty.

Flat Data

Values are stored packed as f32s (or f64s with the use_f64 feature) in flat Vecs.

For example, the positions member of a Mesh will contain [x, y, z, x, y, z, ...] which you can then use however you like.

Indices

Indices are also loaded and may re-use vertices already existing in the mesh, this data is stored in the indices member.

When a Mesh contains per vertex per face normals or texture coordinates, positions can be duplicated to be per vertex per face too via the single_index flag. This potentially changes the topology (faces may become disconnected even though their vertices still share a position in space).

By default separate indices for normals and texture coordinates are created. This also guarantees that the topology of the Mesh does not change when either of the latter are specified per vertex per face.

Materials

Standard MTL attributes are supported too. Any unrecognized parameters will be stored in a HashMap containing the key-value pairs of the unrecognized parameter and its value.

Example

In this simple example we load the classic Cornell Box model that only defines positions and print out its attributes. This example is a slightly trimmed down version of print_model_info and print_material_info combined together, see them for a version that also prints out normals and texture coordinates if the model has them.

The LoadOptions used are typical for the case when the mesh is going to be sent to a realtime rendering context (game engine, GPU etc.).

use tobj;

let cornell_box = tobj::load_obj("obj/cornell_box.obj", &tobj::GPU_LOAD_OPTIONS);
assert!(cornell_box.is_ok());

let (models, materials) = cornell_box.expect("Failed to load OBJ file");

// Materials might report a separate loading error if the MTL file wasn't found.
// If you don't need the materials, you can generate a default here and use that
// instead.
let materials = materials.expect("Failed to load MTL file");

println!("# of models: {}", models.len());
println!("# of materials: {}", materials.len());

for (i, m) in models.iter().enumerate() {
    let mesh = &m.mesh;

    println!("model[{}].name = \'{}\'", i, m.name);
    println!("model[{}].mesh.material_id = {:?}", i, mesh.material_id);

    println!(
        "Size of model[{}].face_arities: {}",
        i,
        mesh.face_arities.len()
    );

    let mut next_face = 0;
    for f in 0..mesh.face_arities.len() {
        let end = next_face + mesh.face_arities[f] as usize;
        let face_indices: Vec<_> = mesh.indices[next_face..end].iter().collect();
        println!("    face[{}] = {:?}", f, face_indices);
        next_face = end;
    }

    // Normals and texture coordinates are also loaded, but not printed in this example
    println!("model[{}].vertices: {}", i, mesh.positions.len() / 3);

    assert!(mesh.positions.len() % 3 == 0);
    for v in 0..mesh.positions.len() / 3 {
        println!(
            "    v[{}] = ({}, {}, {})",
            v,
            mesh.positions[3 * v],
            mesh.positions[3 * v + 1],
            mesh.positions[3 * v + 2]
        );
    }
}

for (i, m) in materials.iter().enumerate() {
    println!("material[{}].name = \'{}\'", i, m.name);
    if let Some(ambient) = m.ambient {
        println!(
            "    material.Ka = ({}, {}, {})",
            ambient[0], ambient[1], ambient[2]
        );
    }
    if let Some(diffuse) = m.diffuse {
        println!(
            "    material.Kd = ({}, {}, {})",
            diffuse[0], diffuse[1], diffuse[2]
        );
    }
    if let Some(specular) = m.specular {
        println!(
            "    material.Ks = ({}, {}, {})",
            specular[0], specular[1], specular[2]
        );
    }
    if let Some(shininess) = m.shininess {
        println!("    material.Ns = {}", shininess);
    }
    if let Some(dissolve) = m.dissolve {
        println!("    material.d = {}", dissolve);
    }
    if let Some(ambient_texture) = &m.ambient_texture {
        println!("    material.map_Ka = {}", ambient_texture);
    }
    if let Some(diffuse_texture) = &m.diffuse_texture {
        println!("    material.map_Kd = {}", diffuse_texture);
    }
    if let Some(specular_texture) = &m.specular_texture {
        println!("    material.map_Ks = {}", specular_texture);
    }
    if let Some(shininess_texture) = &m.shininess_texture {
        println!("    material.map_Ns = {}", shininess_texture);
    }
    if let Some(normal_texture) = &m.normal_texture {
        println!("    material.map_Bump = {}", normal_texture);
    }
    if let Some(dissolve_texture) = &m.dissolve_texture {
        println!("    material.map_d = {}", dissolve_texture);
    }

    for (k, v) in &m.unknown_param {
        println!("    material.{} = {}", k, v);
    }
}

Rendering Examples

For an example of integration with glium to make a simple OBJ viewer, check out tobj viewer. Some more sample images can be found in this gallery.

The Rungholt model shown below is reasonably large (6.7M triangles, 12.3M vertices) and is loaded in ~7.47s using a peak of ~1.1GB of memory on a Windows 10 machine with an i7-4790k and 16GB of 1600Mhz DDR3 RAM with tobj 0.1.1 on rustc 1.6.0. The model can be found on Morgan McGuire’s meshes page and was originally built by kescha. Future work will focus on improving performance and memory usage.

Rungholt

For an example of integration within a ray tracer, check out tray_rust’s mesh module. The Stanford Buddha and Dragon from the Stanford 3D Scanning Repository both load quite quickly. The Rust logo model was made by Nylithius on BlenderArtists. The materials used are from the MERL BRDF Database.

Rust logo with friends

Features

  • ahash – On by default. Use AHashMap for hashing when reading files and merging vertices. To disable and use the slower HashMap instead, unset default features in Cargo.toml:

    [dependencies.tobj]
    default-features = false
    
  • merging – Adds support for merging identical vertex positions on disconnected faces during import.

    Warning: this feature uses const generics and thus requires at least a beta toolchain to build.

  • reordering – Adds support for reordering the normal- and texture coordinate indices.

  • async – Adds support for async loading of obj files from a buffer, with an async material loader. Useful in environments that do not support blocking IO (e.g. WebAssembly).

  • [‘use_f64’] - Uses double-precision (f64) instead of single-precision (f32) floating point types

Structs

  • Options for processing the mesh during loading.
  • A material that may be referenced by one or more Meshes.
  • A mesh made up of triangles loaded from some OBJ file.
  • A named model within the file.

Enums

  • Possible errors that may occur while loading OBJ and MTL files.

Constants

Functions

  • Load the materials defined in a MTL file.
  • Load the various materials in a MTL buffer.
  • Load the various objects specified in the OBJ file and any associated MTL file.
  • Load the various meshes in an OBJ buffer.
  • Load the various meshes in an OBJ buffer.

Type Aliases

  • A Result containing all the models loaded from the file and any materials from referenced material libraries. Or an error that occured while loading.
  • A Result containing all the materials loaded from the file and a map of MTL name to index. Or an error that occured while loading.