Giter Site home page Giter Site logo

guybrush77 / rapidobj Goto Github PK

View Code? Open in Web Editor NEW
171.0 5.0 14.0 3.59 MB

A fast, header-only, C++17 library for parsing Wavefront .obj files.

License: MIT License

CMake 1.62% C++ 96.95% Python 1.43%
3d-graphics loader parser cpp cpp17 single-header-lib wavefront

rapidobj's Introduction

rapidobjrapidobj

Standard License Platform Build Status

About

rapidobj is an easy-to-use and fast single-header C++17 library that loads and parses Wavefront .obj files.

The .obj file format was first used by Wavefront Technologies around 1990. However, this 3D geometry file format did not age well. An .obj file is a text file and, consequently, large models take a lot of of disk space and are slow to load and parse. Moreover, after loading and parsing, additional processing steps are required to transform the data into a format suitable for hardware (i.e. GPU) rendering. Nevertheless, .obj files are common enough that it's useful to have an efficient way to parse them.

Benchmarks

rapidobj's API was influenced by another single header C++ library, tinyobjloader. From users' point of view, the two libraries look fairly similar. That said, tinyobjloader has been around for some time; it is a mature and well tested library. So, why use rapidobj library? It is fast, and especially so when parsing large files. It was designed to take full advantage of modern computer hardware.

See Benchmarks page.

For an independent evaluation of this and other .obj parsers, check out Comparing .obj parse libraries.

Integration

Prerequisites

You will need a C++ compiler that fully supports C++17 standard. In practice, this means:

  • GCC 8 or higher
  • MSVC 19.14 or higher
  • Clang 7 or higher

If you intend to use CMake as your build system, you will need to install CMake version 3.20 or higher.

Manual Integration

The simplest way to integrate the library in your project is to copy the header file, rapidobj.hpp, to a location that is in your compiler's include path. To use the library from your application, include the header file:

#include "rapidobj.hpp"

To compile your project, make sure to use the C++17 switch (-std=c++17 for g++ and clang, /std:c++17 for MSVC).

There are some extra considerations when building a Linux project: you need to link your application against libpthread library. For example, assuming g++ compiler:

g++ -std=c++17 my_src.cpp -pthread -o my_app

๐Ÿ“„ If you are using gcc version 8, you also have to link against the stdc++fs library (std::filesystem used by rapidobj is not part of libstdc++ until gcc version 9).

CMake Integration

External

This section explains how to use rapidobj external to your project. If using the command line, perform cmake configuration and generation steps from inside the rapidobj folder:

cmake -B build .

The next step is to actually install the rapidobj package:

cd build
sudo make install

The install command will copy the files to well defined system directories. Note that this command will likely require administrative access.

The only remaining step is to find the rapidobj package from inside your own CMakeLists.txt file and link against it. For example:

add_executable(my_app my_src.cpp)

find_package(RapidObj REQUIRED)

target_link_libraries(my_app PRIVATE rapidobj::rapidobj)

rapidobj cmake script places the header file in a rapidobj subfolder of the include directory. Consequently, the include directive in your code should look like this:

#include "rapidobj/rapidobj.hpp"

What if you don't want to install rapidobj in a system directory? rapidobj allows you to specify custom install folders. CMake cache variable RAPIDOBJ_INCLUDE_DIR is used to set header file install location; RAPIDOBJ_CMAKE_DIR is used to set cmake files install location. For example, to install rapidobj in a folder local inside your home directory, the cmake configuration and generation steps are as follows:

cmake -B build -DRAPIDOBJ_INCLUDE_DIR=${HOME}/local/include -DRAPIDOBJ_CMAKE_DIR=${HOME}/local/cmake .

The install step is almost the same as before:

cd build
make install

The only difference is that administrative access (i.e. sudo) is no longer required since the destination is users' home folder, as opposed to system folders.

Because the files have been installed to a custom location that CMake does not know about, CMake cannot find the rapidobj package automatically. We can fix this by providing a hint about the cmake directory whereabouts:

add_executable(my_app my_src.cpp)

find_package(RapidObj REQUIRED HINTS $ENV{HOME}/local/cmake)

target_link_libraries(my_app PRIVATE rapidobj::rapidobj)

Once the package has been successfully installed, rapidobj directory can be deleted.

Embedded

Another way to use rapidobj is to embed it inside your project. In your project's root, create a folder named thirdparty and then copy rapidobj to this folder. Installation is not required; it is sufficient to add rapidobj's subfolder to your project:

add_executable(my_app my_src.cpp)

add_subdirectory(thirdparty/rapidobj)

target_link_libraries(my_app PRIVATE rapidobj::rapidobj)

If you do not wish to manually download and place rapidobj files, you can automate these steps by using CMake's FetchContent module:

add_executable(my_app my_src.cpp)

include(FetchContent)

FetchContent_Declare(rapidobj
    GIT_REPOSITORY  https://github.com/guybrush77/rapidobj.git
    GIT_TAG         origin/master)

FetchContent_MakeAvailable(rapidobj)

target_link_libraries(my_app PRIVATE rapidobj::rapidobj)

API

ParseFile

Loads a Wavefront .obj data from a file, parses it and returns a binary Result object.

Signature:

Result ParseFile(
    const std::filesystem::path& obj_filepath,
    const MaterialLibrary&       mtl_library = MaterialLibrary::Default());

Parameters:

  • obj_filepath - Path to .obj file to be parsed.
  • mtl_library - MaterialLibrary object specifies .mtl file search path(s) and loading policy.

Result:

  • Result - The .obj file data in a binary format.
Show examples
rapidobj::Result result = rapidobj::ParseFile("/home/user/teapot/teapot.obj");

ParseStream

Loads a Wavefront .obj data from a standard library input stream, parses it and returns a binary Result object. Because input streams are sequential, this function is usually less performant than the similar ParseFile function.

Signature:

Result ParseStream(
    std::istream&          obj_stream,
    const MaterialLibrary& mtl_library = MaterialLibrary::Default());

Parameters:

  • obj_filepath - Input stream to parse.
  • mtl_library - MaterialLibrary object specifies .mtl file search path(s) and loading policy.

Result:

  • Result - The .obj file data in a binary format.
Show examples
std::ifstream stream("/home/user/teapot/teapot.obj");
rapidobj::Result result = rapidobj::ParseStream(stream);

MaterialLibrary

An object of type MaterialLibrary is used as an argument for the Parse functions. It informs these functions how materials are to be handled.

Signature:

struct MaterialLibrary final {
    static MaterialLibrary Default();
    static MaterialLibrary Default(Load policy);
    static MaterialLibrary SearchPath(std::filesystem::path path, Load policy = Load::Mandatory);
    static MaterialLibrary SearchPaths(std::vector<std::filesystem::path> paths,
                                       Load policy = Load::Mandatory);
    static MaterialLibrary String(std::string_view text);
    static MaterialLibrary Ignore();
};

Default()

A convenience constructor.

For ParseFile, this constructor is identical to MaterialLibrary::SearchPath(".", Load::Mandatory).

For ParseStream, this constructor is identical to MaterialLibrary::Ignore(), except that Mesh::material_ids will be populated.

Show examples
// Default search assumes .obj and .mtl file are in the same folder.
//  
// home
// โ””โ”€โ”€ user
//     โ””โ”€โ”€ teapot
//         โ”œโ”€โ”€ teapot.mtl
//         โ””โ”€โ”€ teapot.obj
Result result = ParseFile("/home/user/teapot/teapot.obj", MaterialLibrary::Default());
// MaterialLibrary::Default can be omitted since it is the default argument of ParseFile.
//
// home
// โ””โ”€โ”€ user
//     โ””โ”€โ”€ teapot
//         โ”œโ”€โ”€ teapot.mtl
//         โ””โ”€โ”€ teapot.obj
Result result = ParseFile("/home/user/teapot/teapot.obj");
// MaterialLibrary::Default can be omitted since it is the default argument of ParseStream.
// Material library will not be parsed, but Mesh::material_ids will be populated.
//
Result result = ParseStream(input_stream);

Default(Load policy)

A convenience constructor for ParseFile only.

Identical to MaterialLibrary::SearchPath(".", policy).

Show examples
// Look for the teapot.mtl file in the teapot folder.
// This call will not generate an error if teapot.mtl cannot be found.
//  
// home
// โ””โ”€โ”€ user
//     โ””โ”€โ”€ teapot
//         โ””โ”€โ”€ teapot.obj
Result result = ParseFile("/home/user/teapot/teapot.obj", MaterialLibrary::Default(Load::Optional));

SearchPath(std::filesystem::path path, Load policy)

Constructor used to specify .mtl file's relative or absolute search path and loading policy.

A relative search path's current directory is .obj file's parent folder. A relative search path only applies to the ParseFile function; specifying a relative search path for the ParseStream function will generate an error.

Show examples
// Look for .mtl file in subfolder 'materials' using a relative search path.
//
// home
// โ””โ”€โ”€ user
//     โ””โ”€โ”€ teapot
//         โ”œโ”€โ”€ materials
//         โ”‚ย ย  โ””โ”€โ”€ teapot.mtl
//         โ””โ”€โ”€ teapot.obj
Result result = ParseFile("/home/user/teapot/teapot.obj",
                          MaterialLibrary::SearchPath("materials"));
// Look for .mtl file in an arbitrary file folder using an absolute search path.
//
// home
// โ””โ”€โ”€ user
//     โ”œโ”€โ”€ materials
//     โ”‚ย ย  โ””โ”€โ”€ teapot.mtl
//     โ””โ”€โ”€ teapot
//         โ””โ”€โ”€ teapot.obj
Result result = ParseFile("/home/user/teapot/teapot.obj",
                          MaterialLibrary::SearchPath("/home/user/materials"));

SearchPaths(std::vector<std::filesystem::path> paths, Load policy)

Constructor used to specify multiple .mtl file's relative or absolute search paths and loading policy. The paths are examined in order; the first .mtl file found will be loaded and parsed.

A relative search path's current directory is .obj file's parent folder. A relative search path only applies to the ParseFile function; specifying a relative search path for the ParseStream function will generate an error.

Show examples
// Look for .mtl file in folder /home/user/teapot/materials,
// then /home/user/materials, then /home/user/teapot.
//
// home
// โ””โ”€โ”€ user
//     โ”œโ”€โ”€ materials
//     โ”‚ย ย  โ””โ”€โ”€ teapot.mtl
//     โ””โ”€โ”€ teapot
//         โ”œโ”€โ”€ materials
//         โ”‚ย ย  โ””โ”€โ”€ teapot.mtl
//         โ”œโ”€โ”€ teapot.mtl
//         โ””โ”€โ”€ teapot.obj
MaterialLibrary mtllib = MaterialLibrary::SearchPaths({ "materials", "../materials", "." });
Result          result = ParseFile("/home/user/teapot/teapot.obj", mtllib);

String(std::string_view text)

Constructor used to provide .mtl material description as a string.

Show examples
// Define a material library as a string, then pass it to ParseFile function.
//
// home
// โ””โ”€โ”€ user
//     โ””โ”€โ”€ teapot
//         โ””โ”€โ”€ teapot.obj
static constexpr auto materials = R"(
    newmtl red
    Kd 1 0 0
)";
Result result = ParseFile("/home/user/teapot/teapot.obj", MaterialLibrary::String(materials));

Ignore()

Constructor used to instruct Parse functions to completely ignore any material libraries, regardless of whether they are present or not. Result Materials array will be empty. Moreover, all Mesh::material_ids arrays will be empty.

Show examples
// Instruct ParseFile to ignore teapot.mtl file.
//
// home
// โ””โ”€โ”€ user
//     โ””โ”€โ”€ teapot
//         โ”œโ”€โ”€ teapot.mtl
//         โ””โ”€โ”€ teapot.obj
Result result = ParseFile("/home/user/teapot/teapot.obj", MaterialLibrary::Ignore());

Load Policy

Load is passed as an argument to MaterialLibrary SearchPath(s) constructors to specify which actions to take if the material library file cannot be opened.

Mandatory loading instructs the ParseFile and ParseStream functions to immediately stop further execution and produce an error if the .mtl file cannot be opened.

Optional loading instructs the Parse functions to continue .obj loading and parsing, even if the .mtl file cannot be opened. No error will be generated. However, in this case, the Materials array will be empty. Mesh::material_ids will contain ids assigned in order of appearance in the .obj file (0, 1, 2 etc.).

Signature:

enum class Load { Mandatory, Optional };
Show examples
// Look for .mtl file in subfolder 'materials'.
// If the file cannot be found, ParseFile function will fail with an error.
//
MaterialLibrary mtllib = MaterialLibrary::SearchPath("materials", Load::Mandatory);
Result          result = ParseFile("/home/user/teapot/teapot.obj", mtllib);
// Look for .mtl file in subfolder 'materials'.
// If the file cannot be found, ParseFile function will continue .obj loading and parsing.
//
MaterialLibrary mtllib = MaterialLibrary::SearchPath("materials", Load::Optional);
Result          result = ParseFile("/home/user/teapot/teapot.obj", mtllib);
// Look for .mtl file in default location.
// If the file cannot be found, ParseFile function will continue .obj loading and parsing.
//
MaterialLibrary mtllib = MaterialLibrary::Default(Load::Optional);
Result          result = ParseFile("/home/user/teapot/teapot.obj", mtllib);

Triangulate

Triangulate all meshes in the Result object.

Signature:

bool Triangulate(Result& result)

Parameters:

Result:

  • bool - True if triangulation was successful; false otherwise.
Show examples
Result result  = ParseFile("/home/user/teapot/teapot.obj");
bool   success = Triangulate(result);

Data Layout

Result

Result object is the return value of the ParseFile and ParseStream functions. It contains the .obj and .mtl file data in binary format.

rapidobj::Result rapidobj::Result

Attributes

Attributes class contains four linear arrays which store vertex positions, texture coordinates, normals and colors data. The element value type is 32-bit float. Only vertex positions are mandatory. Texture coordinates, normals and color attribute arrays can be empty. Array elements are interleaved as { x, y, z } for positions and normals, { u, v } for texture coordinates and { r, g, b } for colors.

Attributes::positions

p0 p1 p2 ... pN-1
xyz xyz xyz xyz

Attributes::texcoords

t0 t1 t2 ... tN-1
uv uv uv uv

Attributes::normals

n0 n1 n2 ... nN-1
xyz xyz xyz xyz

Attributes::colors

c0 c1 c2 ... cN-1
rgb rgb rgb rgb

Shape

Shape is a polyhedral mesh (Mesh), a set of polylines (Lines) or a set of points (Points).

Mesh

Mesh class defines the shape of a polyhedral object. The geometry data is stored in two arrays: indices and num_face_vertices. Per face material information is stored in the material_ids array. Smoothing groups, used for normal interpolation, are stored in the smoothing_group_ids array.

Mesh::indices

The indices array is a collection of faces formed by indexing into vertex attribute arrays. It is a linear array of Index objects. Index class has three fields: position_index, texcoord_index, and normal_index. Only the position_index is mandatory; a vertex normal and UV coordinates are optional. For optional attributes, invalid index (-1) is stored in the normal_index and texcoord_index fields.

f0 f1 f2 ... fN-1
i0i1i2 i3i4i5i6i7 i8i9i10i11 iN-3iN-2iN-1

Mesh::num_face_vertices

A mesh face can have three (triangle), four (quad) or more vertices. Because the indices array is flat, extra information is required to identify which indices are associated with a particular face. The number of vertices for each face [3..255] is stored in the num_face_vertices array. The size of the num_face_vertices array is equal to the number of faces in the mesh. For example, a mesh whose first face is a triangle, second face a pentagon, third face a quadrilateral and last face a triangle, would store the following data:

f0 f1 f2 ... fN-1
3543

Mesh::material_ids

Material IDs index into the the Materials array.

f0 f1 f2 ... fN-1
m0m1m2mN-1

Mesh::smoothing_group_ids

Smoothing group IDs can be used to calculate vertex normals.

f0 f1 f2 ... fN-1
s0s1s2sN-1

Lines

Lines class contains a set of polylines. The geometry data is stored in two arrays: indices and num_line_vertices.

Lines::indices

The indices array defines polylines by indexing into vertex attribute arrays. It is a linear array of Index objects. Index class has three fields: position_index, texcoord_index, and normal_index. The position_index is mandatory. UV coordinates are optional. If UV coordinates are not present, invalid index (-1) is stored in the texcoord_index fields. The normal_index is always set to invalid index.

l0 l1 l2 ... lN-1
i0i1i2i3i4i5 i6i7 i8i9i10i11 iN-5iN-4iN-3iN-2iN-1

Lines::num_line_vertices

A polyline can have two or more vertices. Because the indices array is flat, extra information is required to identify which indices are associated with a particular polyline. The number of vertices for each polyline [2..231) is stored in the num_line_vertices array. The size of the num_line_vertices array is equal to the number of polylines.

l0 l1 l2 ... lN-1
6245

Points

Points class contains a set of points. The geometry data is stored in the indices array.

Points::indices

The indices array defines points by indexing into vertex attribute arrays. It is a linear array of Index objects. Index class has three fields: position_index, texcoord_index, and normal_index. The position_index is mandatory. UV coordinates are optional. If UV coordinates are not present, invalid index (-1) is stored in the texcoord_index fields. The normal_index is always set to invalid index.

p0 p1 p2 ... pN-1
i0i1i2iN-1

Materials

After ParseFile or ParseStream function loads and parses the .mtl file, all the material information is stored in the Result Materials array.

Material Parameters

Parameter Keyword Type Description
ambient Ka color Ambient reflectance
diffuse Kd color Diffuse reflectance
specular Ks color Specular reflectance
transmittance Kt color Transparency
emission Ke color Emissive coeficient
shininess Ns float Specular exponent
ior Ni float Index of refraction
dissolve d float Fake transparency
illum illum int Illumination model
ambient_texname map_Ka path Path to ambient texture
diffuse_texname map_Kd path Path to diffuse texture
specular_texname map_Ks path Path to specular texture
specular_highlight_texname map_Ns path Path to specular highlight texture
bump_texname map_bump path Path to bump map texture
displacement_texname disp path Path to displacement map texture
alpha_texname map_d path Path to alpha texture
reflection_texname refl path Path to reflection map texture
ambient_texopt TextureOption Ambient texture options
diffuse_texopt TextureOption Diffuse texture options
specular_texopt TextureOption Specular texture options
specular_highlight_texopt TextureOption Specular highlight texture options
bump_texopt TextureOption Bump map texture options
displacement_texopt TextureOption Displacement map texture options
alpha_texopt TextureOption Alpha texture options
reflection_texopt TextureOption Reflection map texture options

Material Parameters (PBR Extension)

Parameter Keyword Type Description
roughness Pr float Surface roughness
metallic Pm float Surface metalness
sheen Ps float Amount of soft reflection near edges
clearcoat_thickness Pc float Extra white specular layer on top
clearcoat_roughness Pcr float Roughness of white specular layer
anisotropy aniso float Anisotropy for specular reflection
anisotropy_rotation anisor float Anisotropy rotation amount
roughness_texname map_Pr path Path to roughness texture
metallic_texname map_Pm path Path to metalness texture
sheen_texname map_Ps path Path to sheen texture
emissive_texname map_Ke path Path to emissive texture
normal_texname norm path Path to normal texture
roughness_texopt TextureOption Roughness texture options
metallic_texopt TextureOption Metalness texture options
sheen_texopt TextureOption Sheen texture options
emissive_texopt TextureOption Emissive texture options
normal_texopt TextureOption Normal texture options

TextureOption

Parameter Keyword Type Description
type type TextureType Mapping type (None, Sphere, Cube)
sharpness boost float Boosts mip-map sharpness
brightness mm float Controls texture brightness
contrast mm float Controls texture contrast
origin_offset o float3 Moves texture origin
scale s float3 Adjusts texture scale
turbulence t float3 Controls texture turbulence
texture_resolution texres int Texture resolution to create
clamp clamp bool Only render texels in clamped range
imfchan imfchan char Specifies channels of the file to use
blendu blendu bool Set horizontal texture blending
blendv blendv bool Set vertical texture blending
bump_multiplier bm float Bump map multiplier

Example

Suppose we want to find out the total number of triangles in an .obj file. This can be accomplished by passing the .obj file path to the ParseFile function and then calling the Triangulate function. The next step is looping through all the meshes; in each iteration, the number of triangles in the current mesh is added to the running sum. The code for this logic is shown below:

#include "rapidobj/rapidobj.hpp"

#include <iostream>

int main()
{
    rapidobj::Result result = rapidobj::ParseFile("/path/to/my.obj");

    if (result.error) {
        std::cout << result.error.code.message() << '\n';
        return EXIT_FAILURE;
    }

    bool success = rapidobj::Triangulate(result);

    if (!success) {
        std::cout << result.error.code.message() << '\n';
        return EXIT_FAILURE;
    }

    size_t num_triangles{};

    for (const auto& shape : result.shapes) {
        num_triangles += shape.mesh.num_face_vertices.size();
    }

    std::cout << "Shapes:    " << result.shapes.size() << '\n';
    std::cout << "Materials: " << result.materials.size() << '\n';
    std::cout << "Triangles: " << num_triangles << '\n';

    return EXIT_SUCCESS;
}

Next Steps

Typically, parsed .obj data cannot be used "as is". For instance, for hardware rendering, a number of additional processing steps are required so that the data is in a format easily consumed by a GPU. rapidobj provides one convenience function, Triangulate, to assist with this task. Other tasks must be implemented by the rendering application. These may include:

  • Gathering all the attributes so that the vertex data is in a single array of interleaved attributes. This step may optionally include vertex deduplication.
  • Generate normals in case they are not provided in the .obj file. This step may use smoothing groups (if any) to create higher quality normals.
  • Optionally optimise the meshes for rendering based on some criteria such as: material type, mesh size, number of batches to be submitted, etc.

OS Support

  • Windows
  • macOS
  • Linux

Third Party Tools and Resources

This is a list of third party tools and resources used by this project:

rapidobj's People

Contributors

asmaloney avatar guybrush77 avatar otreblan avatar stripe2933 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

rapidobj's Issues

Load from memory

Hello, This library seems very nice.
Would it be possible to add the ability to open files from std::istream ?
Thanks !

Warnings on indices types

Hello,
Is there any reason why the Indices fields are int and not std::size_t like in the Array class ?

struct Index final {
    int position_index;
    int texcoord_index;
    int normal_index;
};

Wouldn't std::size_t be preferable in that case ?

struct Index final {
    std::size_t position_index{};
    std::size_t texcoord_index{};
    std::size_t normal_index{};
};

Triangulate causing missing triangles?

Hi,

Trianglulate used on this file appears to be causing triangles to go missing:

https://murena.io/s/Z6MSrKkCf2Mr72Y

Screenshot showing some of the missing triangles outlined in red here:

triangulate_bug

If I skip the triangulate step and do my own simple triangulate while loading (which effectively just treats all faces like a triangle fan), everything loads correctly.

[edit]
Ok, it looks like triangulate is flipping some triangles. If I force the material to be double sided, the missing triangles re-appear.

Changing lines 6983-ish to this (which basically just duplicates my loader code) fixes the problem with this model:

		dst->indices[idst + 0] = index0;
		dst->indices[idst + 1] = index1;
		dst->indices[idst + 2] = index2; // d02_is_less ? index2 : index3;
		dst->indices[idst + 3] = index0; // d02_is_less ? index0 : index1;
		dst->indices[idst + 4] = index2;
		dst->indices[idst + 5] = index3;

Not sure what d02_is_less is supposed to do though so probably not that simple.
[/edit]

Bye,
Mark

Material Id == -1?

Hi,

I am trying to get a model loading, but all the material id's are returned as -1.

The model has an .mtl file which appears to be loading OK as there is an array of 2 materials in the loaded result and 2 materials in the mtl file.

The model works OK if I 'force' material id to 1 (the last material in the .mtl file) but just thought this might be a bug and, if not, what should I be doing with material id's of -1?

Of course the model could be corrupt up - it's a model of a vive VR controller that comes with steam vr and if you have steamvr you can find it in there somewhere, if not I'd be happy to send it to you.

Bye!
Mark

Make CTest Optional

We use rapidobj in a project and noticed that it includes CTest. However, since we perform our unit tests with catch2, CTest is not needed.

I would suggest that we simply add a global define that allows projects using Rapidobj to skip include(CTest) or any other test-related projects.

Limiting thread count support

In rapidobj.hpp:7085 the thread count is hardcoded to

auto num_threads = std::thread::hardware_concurrency();

Just asking if there is any chance to vary this from the ReadFile API or by altering some config/prefs. The usecase of that if user want to force rapidobj to load in 1 thread, e.x. while having parallelism on higher scale. Also, as far as i understand, single/multi threaded approach depends strictly on file size, which is clever, but not very flexible.

Add some progress/callback for file loading

Just asking about some progress callback API. It would be convenient to pass something like OpenImage::ProgressCallback onto the rapidobj::ParseFile. E.x.

/// Pointer to a function called periodically by ParseFile.
/// It returns a bool, which if 'true' will STOP the read.
typedef bool (*ProgressCallback)(float portion_done);

inline Result ParseFile(
    const std::filesystem::path& obj_filepath,
    const MaterialLibrary&       mtl_library = MaterialLibrary::Default(),
    const ProgressCallback&      progress_callback = nullptr);

As shown, it also can be used to terminate the reading, returning an error probably. It's a QoL for loading large files / using slow PC.

Add Emscripten Support?

Hi,

I had a quick go at building rapidobj for Emscripten, and got it working with minimal changes! However, my hacks could probably do with some tidying up.

I just reused the __linux #includes and File/FileReader etc class definitons for emscripten which mostly worked except I had to comment out the call to the readahead function. But I'm guessing this largely or even totally negates all the threading going on? Note that emscripten doesn't have access to a 'real' filesystem - it just fakes C I/O on a blob object that gets shipped with emscripten apps, so 'file io' is really just a bunch of synchronous memcpy's. This may change over time though, if a filesystem API for the browser ever gets finalized.

Threads in emscripten are a bit tricky as you have to preallocate them before the app starts via a PTHREAD_POOL_SIZE variable. Plus they're really WebWorkers which ar4e more heavyweight than plain pthreads so you don't want to use too many of them. Rapidobj seems to use std::thread::hardware_concurrency()+5 threads, ie: I had to set PTHREAD_POOL_SIZE to 23 on my 16 thread machine to get it to work. Do you have any idea what this +5 is about? Will it always be '5' everywhere? I guess it could be something going on with esmcripten too, will look into it.

So I think there needs to be some way in rapidobj to at least clamp max thread usage, eg: perhaps replace calls to std::thread::hardware_concurrency() with something like std::min(std::thread::hardware_concurrency(), RAPIDOBJ_MAX_THREADS) - 5

Of course without readahead() tghrading could be pointless, but RAPIDOBJ_MAX_THREADS seems like the easiest way to go unless you're keen on adding something like RAPIDOBJ_SINGLE_THREADED? I had a quick look at doing this but there is a LOT of scary thread code in there!

I also had a quick look at LoadStream in the hope it was single threaded, but that seems to use threads too, not sure how many though, if it's a constant number than it may even be easier to just redirect LoadFile to LoadStream in emscripten.

Anyway, I'll probably create a fork of rapidobj with my emscripten changes, I'll you you know when it's done.

Bye!
Mark

materials generally unnecessary

Could materials be considered optional? Currently, Merge() returns prematurely when it hasn't found a material file, and then the geometry won't be loaded at all. Is it safe to just comment out those lines?

In my experience, when .OBJ files are used, it's only the geometry that's of interest, and material files are likely to be ignored. It's not like they can describe a decent shader by today's standards, and they rarely contain more than a single shade of grey or the name of a single texture map anyway.

It's good to be able to see where multiple materials may exist, of course, but I don't think their absence needs to be a show-stopping error.

Great work, by the way! Millions of triangles per second! Most 3D applications can't even open their own binary formats that fast.

Need to Update Documentation

  • Document the MaterialLibrary API
  • Remove the "house" (quad + triangle) example
  • Support github light/dark themes

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.