19 Commits

Author SHA1 Message Date
Syoyo Fujita
2daec8be53 Search .mtl and texture from .obj's base dir.
Fix bad memory access.
2016-12-07 00:54:12 +09:00
Syoyo Fujita
c2ff3f12fc Support multiple filenames for mtllib line. Fixes #112. 2016-12-06 23:44:55 +09:00
Syoyo Fujita
e0b39341fc Merge pull request #111 from longjon/python2
Support Python 2.7
2016-12-06 14:37:05 +09:00
Syoyo Fujita
947582b592 Merge pull request #110 from longjon/python-attribs
(Re-)expose attributes to Python
2016-12-06 14:36:14 +09:00
Jonathan L Long
c207ff3561 Support Python 2.7. 2016-12-05 15:24:22 -08:00
Jonathan L Long
41dd7c806e (Re-)expose attribs to Python. 2016-12-05 15:12:28 -08:00
Syoyo Fujita
aa4dabe64f Describe default behavior of mtl_basedir. 2016-11-23 17:17:35 +09:00
Syoyo Fujita
9868630d0e Fix windows build of loader_example. 2016-11-04 12:36:15 +09:00
Syoyo Fujita
f2397573f3 Merge pull request #107 from Warezovvv/master
Little intuitive improvements
2016-11-03 17:39:16 +09:00
Nikita Krupitskas
7d5699118e Little intuitive improvements 2016-11-03 11:23:21 +03:00
Syoyo Fujita
0948ca0417 Add more support for parsing texture options.
Add more unit testing for texture options.
2016-11-03 02:58:44 +09:00
Syoyo Fujita
582eb2b818 Initial support of texture options. 2016-11-02 02:11:17 +09:00
Syoyo Fujita
c2474e27ab Fix seg fault when no material assigned to object in viewer example.
Bump version v1.0.2.
2016-10-24 23:58:33 +09:00
Syoyo Fujita
6c6390f034 Fix typo. 2016-10-24 23:58:23 +09:00
Syoyo Fujita
4d6649cc6d Suppress compiler warnings. 2016-10-24 23:54:20 +09:00
Syoyo Fujita
d543b1447f Merge pull request #105 from dotdash/speedup
Improve parsing speed by about a factor of 2 for large files
2016-10-24 23:37:34 +09:00
Björn Steinbrink
d6eeb14216 Avoid unnecessary ldexp() and pow() calls
Parse times for some large .obj files (without asan):

         File A  File B  File C
Before   1239ms   294ms   271ms
After    1037ms   203ms   190ms
2016-10-24 14:47:59 +02:00
Björn Steinbrink
aa670fe91e Use a lookup table to speed up float parsing
The pow() function is pretty expensive, so creating a small lookup
table for the first few negative powers of ten provides a big speedup.

Parse times for some large .obj files (without asan):

        File A  File B  File C
Before  2500ms   573ms   545ms
After   1239ms   294ms   271ms
2016-10-24 12:15:42 +02:00
Björn Steinbrink
ebdbd8a231 Avoid unnecessary reallocations of the "linebuf" string
safeGetline() already clears the string buffer before writing to it, so
the same buffer can be used multiple times, but the loops calling
safeGetline() have the string scoped within the loop, so its
constructed and destructed in each loop iteration, causing lots of
unnecessary allocations.

Parse times for some large .obj files (without asan):

        File A  File B  File C
Before  2743ms   589ms   615ms
After   2500ms   573ms   545ms
2016-10-24 12:13:56 +02:00
11 changed files with 697 additions and 128 deletions

View File

@@ -143,7 +143,26 @@ float eye[3], lookat[3], up[3];
GLFWwindow* window; GLFWwindow* window;
void CheckErrors(std::string desc) { static std::string GetBaseDir(const std::string &filepath) {
if (filepath.find_last_of("/\\") != std::string::npos)
return filepath.substr(0, filepath.find_last_of("/\\"));
return "";
}
static bool FileExists(const std::string &abs_filename) {
bool ret;
FILE *fp = fopen(abs_filename.c_str(), "rb");
if (fp) {
ret = true;
fclose(fp);
} else {
ret = false;
}
return ret;
}
static void CheckErrors(std::string desc) {
GLenum e = glGetError(); GLenum e = glGetError();
if (e != GL_NO_ERROR) { if (e != GL_NO_ERROR) {
fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e); fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e);
@@ -151,7 +170,7 @@ void CheckErrors(std::string desc) {
} }
} }
void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) { static void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) {
float v10[3]; float v10[3];
v10[0] = v1[0] - v0[0]; v10[0] = v1[0] - v0[0];
v10[1] = v1[1] - v0[1]; v10[1] = v1[1] - v0[1];
@@ -175,7 +194,7 @@ void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) {
} }
} }
bool LoadObjAndConvert(float bmin[3], float bmax[3], static bool LoadObjAndConvert(float bmin[3], float bmax[3],
std::vector<DrawObject>* drawObjects, std::vector<DrawObject>* drawObjects,
std::vector<tinyobj::material_t>& materials, std::vector<tinyobj::material_t>& materials,
std::map<std::string, GLuint>& textures, std::map<std::string, GLuint>& textures,
@@ -187,9 +206,16 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
tm.start(); tm.start();
std::string base_dir = GetBaseDir(filename);
#ifdef _WIN32
base_dir += "\\";
#else
base_dir += "/";
#endif
std::string err; std::string err;
bool ret = bool ret =
tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, NULL); tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, base_dir.c_str());
if (!err.empty()) { if (!err.empty()) {
std::cerr << err << std::endl; std::cerr << err << std::endl;
} }
@@ -209,6 +235,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
printf("# of materials = %d\n", (int)materials.size()); printf("# of materials = %d\n", (int)materials.size());
printf("# of shapes = %d\n", (int)shapes.size()); printf("# of shapes = %d\n", (int)shapes.size());
// Append `default` material
materials.push_back(tinyobj::material_t());
// Load diffuse textures // Load diffuse textures
{ {
for (size_t m = 0; m < materials.size(); m++) { for (size_t m = 0; m < materials.size(); m++) {
@@ -220,9 +249,20 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
GLuint texture_id; GLuint texture_id;
int w, h; int w, h;
int comp; int comp;
unsigned char* image = stbi_load(mp->diffuse_texname.c_str(), &w, &h, &comp, STBI_default);
std::string texture_filename = mp->diffuse_texname;
if (!FileExists(texture_filename)) {
// Append base dir.
texture_filename = base_dir + mp->diffuse_texname;
if (!FileExists(texture_filename)) {
std::cerr << "Unable to find file: " << mp->diffuse_texname << std::endl;
exit(1);
}
}
unsigned char* image = stbi_load(texture_filename.c_str(), &w, &h, &comp, STBI_default);
if (image == nullptr) { if (image == nullptr) {
std::cerr << "Unable to load texture: " << mp->diffuse_texname << std::endl; std::cerr << "Unable to load texture: " << texture_filename << std::endl;
exit(1); exit(1);
} }
glGenTextures(1, &texture_id); glGenTextures(1, &texture_id);
@@ -248,11 +288,6 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
{ {
for (size_t s = 0; s < shapes.size(); s++) { for (size_t s = 0; s < shapes.size(); s++) {
size_t current_material_id = 0;
if (shapes[s].mesh.material_ids.size() > 0 && shapes[s].mesh.material_ids.size() > s) {
// Base case
current_material_id = shapes[s].mesh.material_ids[s];
}
DrawObject o; DrawObject o;
std::vector<float> vb; // pos(3float), normal(3float), color(3float) std::vector<float> vb; // pos(3float), normal(3float), color(3float)
for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) { for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) {
@@ -260,12 +295,16 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1]; tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1];
tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2]; tinyobj::index_t idx2 = shapes[s].mesh.indices[3 * f + 2];
current_material_id = shapes[s].mesh.material_ids[f]; int current_material_id = shapes[s].mesh.material_ids[f];
if (current_material_id >= materials.size()) { if ((current_material_id < 0) || (current_material_id >= static_cast<int>(materials.size()))) {
std::cerr << "Invalid material index: " << current_material_id << std::endl; // Invaid material ID. Use default material.
current_material_id = materials.size() - 1; // Default material is added to the last item in `materials`.
} }
//if (current_material_id >= materials.size()) {
// std::cerr << "Invalid material index: " << current_material_id << std::endl;
//}
//
float diffuse[3]; float diffuse[3];
for (size_t i = 0; i < 3; i++) { for (size_t i = 0; i < 3; i++) {
diffuse[i] = materials[current_material_id].diffuse[i]; diffuse[i] = materials[current_material_id].diffuse[i];
@@ -364,7 +403,15 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
o.vb = 0; o.vb = 0;
o.numTriangles = 0; o.numTriangles = 0;
o.material_id = current_material_id;
// OpenGL viewer does not support texturing with per-face material.
if (shapes[s].mesh.material_ids.size() > 0 && shapes[s].mesh.material_ids.size() > s) {
// Base case
o.material_id = shapes[s].mesh.material_ids[s];
} else {
o.material_id = materials.size() - 1; // = ID for default material.
}
if (vb.size() > 0) { if (vb.size() > 0) {
glGenBuffers(1, &o.vb); glGenBuffers(1, &o.vb);
glBindBuffer(GL_ARRAY_BUFFER, o.vb); glBindBuffer(GL_ARRAY_BUFFER, o.vb);
@@ -385,7 +432,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
return true; return true;
} }
void reshapeFunc(GLFWwindow* window, int w, int h) { static void reshapeFunc(GLFWwindow* window, int w, int h) {
int fb_w, fb_h; int fb_w, fb_h;
// Get actual framebuffer size. // Get actual framebuffer size.
glfwGetFramebufferSize(window, &fb_w, &fb_h); glfwGetFramebufferSize(window, &fb_w, &fb_h);
@@ -401,7 +448,7 @@ void reshapeFunc(GLFWwindow* window, int w, int h) {
height = h; height = h;
} }
void keyboardFunc(GLFWwindow* window, int key, int scancode, int action, static void keyboardFunc(GLFWwindow* window, int key, int scancode, int action,
int mods) { int mods) {
(void)window; (void)window;
(void)scancode; (void)scancode;
@@ -430,7 +477,7 @@ void keyboardFunc(GLFWwindow* window, int key, int scancode, int action,
} }
} }
void clickFunc(GLFWwindow* window, int button, int action, int mods) { static void clickFunc(GLFWwindow* window, int button, int action, int mods) {
(void)window; (void)window;
(void)mods; (void)mods;
if (button == GLFW_MOUSE_BUTTON_LEFT) { if (button == GLFW_MOUSE_BUTTON_LEFT) {
@@ -457,7 +504,7 @@ void clickFunc(GLFWwindow* window, int button, int action, int mods) {
} }
} }
void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) { static void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) {
(void)window; (void)window;
float rotScale = 1.0f; float rotScale = 1.0f;
float transScale = 2.0f; float transScale = 2.0f;
@@ -484,7 +531,7 @@ void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) {
prevMouseY = mouse_y; prevMouseY = mouse_y;
} }
void Draw(const std::vector<DrawObject>& drawObjects, std::vector<tinyobj::material_t>& materials, std::map<std::string, GLuint>& textures) { static void Draw(const std::vector<DrawObject>& drawObjects, std::vector<tinyobj::material_t>& materials, std::map<std::string, GLuint>& textures) {
glPolygonMode(GL_FRONT, GL_FILL); glPolygonMode(GL_FRONT, GL_FILL);
glPolygonMode(GL_BACK, GL_FILL); glPolygonMode(GL_BACK, GL_FILL);
@@ -503,9 +550,11 @@ void Draw(const std::vector<DrawObject>& drawObjects, std::vector<tinyobj::mater
glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_COLOR_ARRAY);
glEnableClientState(GL_TEXTURE_COORD_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY);
std::string diffuse_texname = materials[o.material_id].diffuse_texname; if ((o.material_id < materials.size())) {
if (diffuse_texname.length() > 0) { std::string diffuse_texname = materials[o.material_id].diffuse_texname;
glBindTexture(GL_TEXTURE_2D, textures[diffuse_texname]); if (textures.find(diffuse_texname) != textures.end()) {
glBindTexture(GL_TEXTURE_2D, textures[diffuse_texname]);
}
} }
glVertexPointer(3, GL_FLOAT, stride, (const void*)0); glVertexPointer(3, GL_FLOAT, stride, (const void*)0);
glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3)); glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3));

74
examples/voxelize/main.cc Normal file
View File

@@ -0,0 +1,74 @@
#define VOXELIZER_IMPLEMENTATION
#include "voxelizer.h"
#define TINYOBJLOADER_IMPLEMENTATION
#include "../../tiny_obj_loader.h"
bool Voxelize(const char* filename, float voxelsizex, float voxelsizey, float voxelsizez, float precision)
{
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename);
if (!err.empty()) {
printf("err: %s\n", err.c_str());
}
if (!ret) {
printf("failed to load : %s\n", filename);
return false;
}
if (shapes.size() == 0) {
printf("err: # of shapes are zero.\n");
return false;
}
// Only use first shape.
{
vx_mesh_t* mesh;
vx_mesh_t* result;
mesh = vx_mesh_alloc(attrib.vertices.size(), shapes[0].mesh.indices.size());
for (size_t f = 0; f < shapes[0].mesh.indices.size(); f++) {
mesh->indices[f] = shapes[0].mesh.indices[f].vertex_index;
}
for (size_t v = 0; v < attrib.vertices.size() / 3; v++) {
mesh->vertices[v].x = attrib.vertices[3*v+0];
mesh->vertices[v].y = attrib.vertices[3*v+1];
mesh->vertices[v].z = attrib.vertices[3*v+2];
}
result = vx_voxelize(mesh, voxelsizex, voxelsizey, voxelsizez, precision);
printf("Number of vertices: %ld\n", result->nvertices);
printf("Number of indices: %ld\n", result->nindices);
}
return true;
}
int
main(
int argc,
char** argv)
{
if (argc < 4) {
printf("Usage: voxelize input.obj voxelsizex voxelsizey voxelsizez precision\n");
exit(-1);
}
const char* filename = argv[1];
float voxelsizex = atof(argv[2]);
float voxelsizey = atof(argv[3]);
float voxelsizez = atof(argv[4]);
float prec = atof(argv[5]);
bool ret = Voxelize(filename, voxelsizex, voxelsizey, voxelsizez, prec);
return ret ? EXIT_SUCCESS : EXIT_FAILURE;
}

View File

@@ -15,8 +15,8 @@
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <mmsystem.h>
#include <windows.h> #include <windows.h>
#include <mmsystem.h>
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
@@ -233,16 +233,17 @@ static void PrintInfo(const tinyobj::attrib_t& attrib,
printf(" material.map_Ns = %s\n", printf(" material.map_Ns = %s\n",
materials[i].specular_highlight_texname.c_str()); materials[i].specular_highlight_texname.c_str());
printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str()); printf(" material.map_bump = %s\n", materials[i].bump_texname.c_str());
printf(" bump_multiplier = %f\n", static_cast<const double>(materials[i].bump_texopt.bump_multiplier));
printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str()); printf(" material.map_d = %s\n", materials[i].alpha_texname.c_str());
printf(" material.disp = %s\n", materials[i].displacement_texname.c_str()); printf(" material.disp = %s\n", materials[i].displacement_texname.c_str());
printf(" <<PBR>>\n"); printf(" <<PBR>>\n");
printf(" material.Pr = %f\n", materials[i].roughness); printf(" material.Pr = %f\n", static_cast<const double>(materials[i].roughness));
printf(" material.Pm = %f\n", materials[i].metallic); printf(" material.Pm = %f\n", static_cast<const double>(materials[i].metallic));
printf(" material.Ps = %f\n", materials[i].sheen); printf(" material.Ps = %f\n", static_cast<const double>(materials[i].sheen));
printf(" material.Pc = %f\n", materials[i].clearcoat_thickness); printf(" material.Pc = %f\n", static_cast<const double>(materials[i].clearcoat_thickness));
printf(" material.Pcr = %f\n", materials[i].clearcoat_thickness); printf(" material.Pcr = %f\n", static_cast<const double>(materials[i].clearcoat_thickness));
printf(" material.aniso = %f\n", materials[i].anisotropy); printf(" material.aniso = %f\n", static_cast<const double>(materials[i].anisotropy));
printf(" material.anisor = %f\n", materials[i].anisotropy_rotation); printf(" material.anisor = %f\n", static_cast<const double>(materials[i].anisotropy_rotation));
printf(" material.map_Ke = %s\n", materials[i].emissive_texname.c_str()); printf(" material.map_Ke = %s\n", materials[i].emissive_texname.c_str());
printf(" material.map_Pr = %s\n", materials[i].roughness_texname.c_str()); printf(" material.map_Pr = %s\n", materials[i].roughness_texname.c_str());
printf(" material.map_Pm = %s\n", materials[i].metallic_texname.c_str()); printf(" material.map_Pm = %s\n", materials[i].metallic_texname.c_str());

View File

@@ -0,0 +1,6 @@
newmtl default
Ka 0 0 0
Kd 0 0 0
Ks 0 0 0
map_Kd tmp.png

View File

@@ -0,0 +1,7 @@
mtllib invalid-file-aaa.mtl invalid-file-bbb.mtl mtllib-multiple-files-issue-112.mtl
o Test
v 1.864151 -1.219172 -5.532511
v 0.575869 -0.666304 5.896140
v 0.940448 1.000000 -1.971128
usemtl default
f 1 2 3

View File

@@ -0,0 +1,36 @@
newmtl default
Ka 0 0 0
Kd 0 0 0
Ks 0 0 0
Kt 0.1 0.2 0.3
map_Ka -clamp on ambient.jpg
map_Kd -o 0.1 diffuse.jpg
map_Ks -s 0.1 0.2 specular.jpg
map_Ns -t 0.1 0.2 0.3 specular_highlight.jpg
map_bump -bm 3 bumpmap.jpg
newmtl bm2
Ka 0 0 0
Kd 0 0 0
Ks 0 0 0
Kt 0.1 0.2 0.3
# blendu
map_Kd -blendu on diffuse.jpg
map_Ks -blendv off specular.jpg
map_Ns -mm 0.1 0.3 specular_highlight.jpg
# -bm after filename
map_bump -imfchan r bumpmap2.jpg -bm 1.5
newmtl bm3
Ka 0 0 0
Kd 0 0 0
Ks 0 0 0
Kt 0.1 0.2 0.3
# type
map_Kd -type sphere diffuse.jpg
map_Ks -type cube_top specular.jpg
map_Ns -type cube_bottom specular_highlight.jpg
map_Ka -type cube_left ambient.jpg
map_d -type cube_right alpha.jpg
map_bump -type cube_front bump.jpg
disp -type cube_back displacement.jpg

View File

@@ -0,0 +1,7 @@
mtllib texture-options-issue-85.mtl
o Test
v 1.864151 -1.219172 -5.532511
v 0.575869 -0.666304 5.896140
v 0.940448 1.000000 -1.971128
usemtl default
f 1 2 3

View File

@@ -1,3 +1,2 @@
* PBR material * PBR material
* Define index_t struct * Define index_t struct
* Python 2.7 binding

View File

@@ -1,4 +1,4 @@
// python3 module for tinyobjloader // python2/3 module for tinyobjloader
// //
// usage: // usage:
// import tinyobjloader as tol // import tinyobjloader as tol
@@ -172,6 +172,7 @@ static PyObject* pyLoadObj(PyObject* self, PyObject* args) {
PyDict_SetItemString(rtndict, "shapes", pyshapes); PyDict_SetItemString(rtndict, "shapes", pyshapes);
PyDict_SetItemString(rtndict, "materials", pymaterials); PyDict_SetItemString(rtndict, "materials", pymaterials);
PyDict_SetItemString(rtndict, "attribs", attribobj);
return rtndict; return rtndict;
} }
@@ -182,10 +183,21 @@ static PyMethodDef mMethods[] = {
}; };
#if PY_MAJOR_VERSION >= 3
static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "tinyobjloader", static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "tinyobjloader",
NULL, -1, mMethods}; NULL, -1, mMethods};
PyMODINIT_FUNC PyInit_tinyobjloader(void) { PyMODINIT_FUNC PyInit_tinyobjloader(void) {
return PyModule_Create(&moduledef); return PyModule_Create(&moduledef);
} }
#else
PyMODINIT_FUNC inittinyobjloader(void) {
Py_InitModule3("tinyobjloader", mMethods, NULL);
}
#endif // PY_MAJOR_VERSION >= 3
} }

View File

@@ -488,6 +488,63 @@ TEST_CASE("usemtl_at_last_line", "[Issue104]") {
REQUIRE(1 == shapes.size()); REQUIRE(1 == shapes.size());
} }
TEST_CASE("texture_opts", "[Issue85]") {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/texture-options-issue-85.obj", gMtlBasePath);
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(1 == shapes.size());
REQUIRE(3 == materials.size());
REQUIRE(0 == materials[0].name.compare("default"));
REQUIRE(0 == materials[1].name.compare("bm2"));
REQUIRE(0 == materials[2].name.compare("bm3"));
REQUIRE(true == materials[0].ambient_texopt.clamp);
REQUIRE(0.1 == Approx(materials[0].diffuse_texopt.origin_offset[0]));
REQUIRE(0.0 == Approx(materials[0].diffuse_texopt.origin_offset[1]));
REQUIRE(0.0 == Approx(materials[0].diffuse_texopt.origin_offset[2]));
REQUIRE(0.1 == Approx(materials[0].specular_texopt.scale[0]));
REQUIRE(0.2 == Approx(materials[0].specular_texopt.scale[1]));
REQUIRE(1.0 == Approx(materials[0].specular_texopt.scale[2]));
REQUIRE(0.1 == Approx(materials[0].specular_highlight_texopt.turbulence[0]));
REQUIRE(0.2 == Approx(materials[0].specular_highlight_texopt.turbulence[1]));
REQUIRE(0.3 == Approx(materials[0].specular_highlight_texopt.turbulence[2]));
REQUIRE(3.0 == Approx(materials[0].bump_texopt.bump_multiplier));
REQUIRE(0.1 == Approx(materials[1].specular_highlight_texopt.brightness));
REQUIRE(0.3 == Approx(materials[1].specular_highlight_texopt.contrast));
REQUIRE('r' == materials[1].bump_texopt.imfchan);
REQUIRE(tinyobj::TEXTURE_TYPE_SPHERE == materials[2].diffuse_texopt.type);
REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_TOP == materials[2].specular_texopt.type);
REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_BOTTOM == materials[2].specular_highlight_texopt.type);
REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_LEFT == materials[2].ambient_texopt.type);
REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_RIGHT == materials[2].alpha_texopt.type);
REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_FRONT == materials[2].bump_texopt.type);
REQUIRE(tinyobj::TEXTURE_TYPE_CUBE_BACK == materials[2].displacement_texopt.type);
}
TEST_CASE("mtllib_multiple_filenames", "[Issue112]") {
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/mtllib-multiple-files-issue-112.obj", gMtlBasePath);
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(1 == materials.size());
}
#if 0 #if 0
int int
main( main(

View File

@@ -23,6 +23,10 @@ THE SOFTWARE.
*/ */
// //
// version 1.0.4 : Support multiple filenames for 'mtllib'(#112)
// version 1.0.3 : Support parsing texture options(#85)
// version 1.0.2 : Improve parsing speed by about a factor of 2 for large
// files(#105)
// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) // version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104)
// version 1.0.0 : Change data structure. Change license from BSD to MIT. // version 1.0.0 : Change data structure. Change license from BSD to MIT.
// //
@@ -42,6 +46,82 @@ THE SOFTWARE.
namespace tinyobj { namespace tinyobj {
// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ...
//
// -blendu on | off # set horizontal texture blending
// (default on)
// -blendv on | off # set vertical texture blending
// (default on)
// -boost float_value # boost mip-map sharpness
// -mm base_value gain_value # modify texture map values (default
// 0 1)
// # base_value = brightness,
// gain_value = contrast
// -o u [v [w]] # Origin offset (default
// 0 0 0)
// -s u [v [w]] # Scale (default
// 1 1 1)
// -t u [v [w]] # Turbulence (default
// 0 0 0)
// -texres resolution # texture resolution to create
// -clamp on | off # only render texels in the clamped
// 0-1 range (default off)
// # When unclamped, textures are
// repeated across a surface,
// # when clamped, only texels which
// fall within the 0-1
// # range are rendered.
// -bm mult_value # bump multiplier (for bump maps
// only)
//
// -imfchan r | g | b | m | l | z # specifies which channel of the file
// is used to
// # create a scalar or bump texture.
// r:red, g:green,
// # b:blue, m:matte, l:luminance,
// z:z-depth..
// # (the default for bump is 'l' and
// for decal is 'm')
// bump -imfchan r bumpmap.tga # says to use the red channel of
// bumpmap.tga as the bumpmap
//
// For reflection maps...
//
// -type sphere # specifies a sphere for a "refl"
// reflection map
// -type cube_top | cube_bottom | # when using a cube map, the texture
// file for each
// cube_front | cube_back | # side of the cube is specified
// separately
// cube_left | cube_right
typedef enum {
TEXTURE_TYPE_NONE, // default
TEXTURE_TYPE_SPHERE,
TEXTURE_TYPE_CUBE_TOP,
TEXTURE_TYPE_CUBE_BOTTOM,
TEXTURE_TYPE_CUBE_FRONT,
TEXTURE_TYPE_CUBE_BACK,
TEXTURE_TYPE_CUBE_LEFT,
TEXTURE_TYPE_CUBE_RIGHT
} texture_type_t;
typedef struct {
texture_type_t type; // -type (default TEXTURE_TYPE_NONE)
float sharpness; // -boost (default 1.0?)
float brightness; // base_value in -mm option (default 0)
float contrast; // gain_value in -mm option (default 1)
float origin_offset[3]; // -o u [v [w]] (default 0 0 0)
float scale[3]; // -s u [v [w]] (default 1 1 1)
float turbulence[3]; // -t u [v [w]] (default 0 0 0)
// int texture_resolution; // -texres resolution (default = ?) TODO
bool clamp; // -clamp (default false)
char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm')
bool blendu; // -blendu (default on)
bool blendv; // -blendv (default on)
float bump_multiplier; // -bm (for bump maps only, default 1.0)
} texture_option_t;
typedef struct { typedef struct {
std::string name; std::string name;
@@ -66,21 +146,39 @@ typedef struct {
std::string displacement_texname; // disp std::string displacement_texname; // disp
std::string alpha_texname; // map_d std::string alpha_texname; // map_d
texture_option_t ambient_texopt;
texture_option_t diffuse_texopt;
texture_option_t specular_texopt;
texture_option_t specular_highlight_texopt;
texture_option_t bump_texopt;
texture_option_t displacement_texopt;
texture_option_t alpha_texopt;
// PBR extension // PBR extension
// http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
float roughness; // [0, 1] default 0 float roughness; // [0, 1] default 0
float metallic; // [0, 1] default 0 float metallic; // [0, 1] default 0
float sheen; // [0, 1] default 0 float sheen; // [0, 1] default 0
float clearcoat_thickness; // [0, 1] default 0 float clearcoat_thickness; // [0, 1] default 0
float clearcoat_roughness; // [0, 1] default 0 float clearcoat_roughness; // [0, 1] default 0
float anisotropy; // aniso. [0, 1] default 0 float anisotropy; // aniso. [0, 1] default 0
float anisotropy_rotation; // anisor. [0, 1] default 0 float anisotropy_rotation; // anisor. [0, 1] default 0
float pad0;
float pad1;
std::string roughness_texname; // map_Pr std::string roughness_texname; // map_Pr
std::string metallic_texname; // map_Pm std::string metallic_texname; // map_Pm
std::string sheen_texname; // map_Ps std::string sheen_texname; // map_Ps
std::string emissive_texname; // map_Ke std::string emissive_texname; // map_Ke
std::string normal_texname; // norm. For normal mapping. std::string normal_texname; // norm. For normal mapping.
texture_option_t roughness_texopt;
texture_option_t metallic_texopt;
texture_option_t sheen_texopt;
texture_option_t emissive_texopt;
texture_option_t normal_texopt;
int pad2;
std::map<std::string, std::string> unknown_parameter; std::map<std::string, std::string> unknown_parameter;
} material_t; } material_t;
@@ -169,15 +267,15 @@ class MaterialReader {
class MaterialFileReader : public MaterialReader { class MaterialFileReader : public MaterialReader {
public: public:
explicit MaterialFileReader(const std::string &mtl_basepath) explicit MaterialFileReader(const std::string &mtl_basedir)
: m_mtlBasePath(mtl_basepath) {} : m_mtlBaseDir(mtl_basedir) {}
virtual ~MaterialFileReader() {} virtual ~MaterialFileReader() {}
virtual bool operator()(const std::string &matId, virtual bool operator()(const std::string &matId,
std::vector<material_t> *materials, std::vector<material_t> *materials,
std::map<std::string, int> *matMap, std::string *err); std::map<std::string, int> *matMap, std::string *err);
private: private:
std::string m_mtlBasePath; std::string m_mtlBaseDir;
}; };
class MaterialStreamReader : public MaterialReader { class MaterialStreamReader : public MaterialReader {
@@ -198,12 +296,13 @@ class MaterialStreamReader : public MaterialReader {
/// 'shapes' will be filled with parsed shape data /// 'shapes' will be filled with parsed shape data
/// Returns true when loading .obj become success. /// Returns true when loading .obj become success.
/// Returns warning and error message into `err` /// Returns warning and error message into `err`
/// 'mtl_basepath' is optional, and used for base path for .mtl file. /// 'mtl_basedir' is optional, and used for base directory for .mtl file.
/// In default(`NULL'), .mtl file is searched from an application's working directory.
/// 'triangulate' is optional, and used whether triangulate polygon face in .obj /// 'triangulate' is optional, and used whether triangulate polygon face in .obj
/// or not. /// or not.
bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
std::vector<material_t> *materials, std::string *err, std::vector<material_t> *materials, std::string *err,
const char *filename, const char *mtl_basepath = NULL, const char *filename, const char *mtl_basedir = NULL,
bool triangulate = true); bool triangulate = true);
/// Loads .obj from a file with custom user callback. /// Loads .obj from a file with custom user callback.
@@ -418,8 +517,14 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) {
read = 1; read = 1;
end_not_reached = (curr != s_end); end_not_reached = (curr != s_end);
while (end_not_reached && IS_DIGIT(*curr)) { while (end_not_reached && IS_DIGIT(*curr)) {
static const double pow_lut[] = {
1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001,
};
const int lut_entries = sizeof pow_lut / sizeof pow_lut[0];
// NOTE: Don't use powf here, it will absolutely murder precision. // NOTE: Don't use powf here, it will absolutely murder precision.
mantissa += static_cast<int>(*curr - 0x30) * pow(10.0, -read); mantissa += static_cast<int>(*curr - 0x30) *
(read < lut_entries ? pow_lut[read] : pow(10.0, -read));
read++; read++;
curr++; curr++;
end_not_reached = (curr != s_end); end_not_reached = (curr != s_end);
@@ -460,7 +565,8 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) {
assemble: assemble:
*result = *result =
(sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent); (sign == '+' ? 1 : -1) *
(exponent ? ldexp(mantissa * pow(5.0, exponent), exponent) : mantissa);
return true; return true;
fail: fail:
return false; return false;
@@ -476,24 +582,72 @@ static inline float parseFloat(const char **token, double default_value = 0.0) {
return f; return f;
} }
static inline void parseFloat2(float *x, float *y, const char **token) { static inline void parseFloat2(float *x, float *y, const char **token,
(*x) = parseFloat(token); const double default_x = 0.0,
(*y) = parseFloat(token); const double default_y = 0.0) {
(*x) = parseFloat(token, default_x);
(*y) = parseFloat(token, default_y);
} }
static inline void parseFloat3(float *x, float *y, float *z, static inline void parseFloat3(float *x, float *y, float *z, const char **token,
const char **token) { const double default_x = 0.0,
(*x) = parseFloat(token); const double default_y = 0.0,
(*y) = parseFloat(token); const double default_z = 0.0) {
(*z) = parseFloat(token); (*x) = parseFloat(token, default_x);
(*y) = parseFloat(token, default_y);
(*z) = parseFloat(token, default_z);
} }
static inline void parseV(float *x, float *y, float *z, float *w, static inline void parseV(float *x, float *y, float *z, float *w,
const char **token) { const char **token, const double default_x = 0.0,
(*x) = parseFloat(token); const double default_y = 0.0,
(*y) = parseFloat(token); const double default_z = 0.0,
(*z) = parseFloat(token); const double default_w = 1.0) {
(*w) = parseFloat(token, 1.0); (*x) = parseFloat(token, default_x);
(*y) = parseFloat(token, default_y);
(*z) = parseFloat(token, default_z);
(*w) = parseFloat(token, default_w);
}
static inline bool parseOnOff(const char **token, bool default_value = true) {
(*token) += strspn((*token), " \t");
const char *end = (*token) + strcspn((*token), " \t\r");
bool ret = default_value;
if ((0 == strncmp((*token), "on", 2))) {
ret = true;
} else if ((0 == strncmp((*token), "off", 3))) {
ret = false;
}
(*token) = end;
return ret;
}
static inline texture_type_t parseTextureType(
const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) {
(*token) += strspn((*token), " \t");
const char *end = (*token) + strcspn((*token), " \t\r");
texture_type_t ty = default_value;
if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) {
ty = TEXTURE_TYPE_CUBE_TOP;
} else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) {
ty = TEXTURE_TYPE_CUBE_BOTTOM;
} else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) {
ty = TEXTURE_TYPE_CUBE_LEFT;
} else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) {
ty = TEXTURE_TYPE_CUBE_RIGHT;
} else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) {
ty = TEXTURE_TYPE_CUBE_FRONT;
} else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) {
ty = TEXTURE_TYPE_CUBE_BACK;
} else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) {
ty = TEXTURE_TYPE_SPHERE;
}
(*token) = end;
return ty;
} }
static tag_sizes parseTagTriple(const char **token) { static tag_sizes parseTagTriple(const char **token) {
@@ -586,6 +740,102 @@ static vertex_index parseRawTriple(const char **token) {
return vi; return vi;
} }
static bool ParseTextureNameAndOption(std::string *texname,
texture_option_t *texopt,
const char *linebuf, const bool is_bump) {
// @todo { write more robust lexer and parser. }
bool found_texname = false;
std::string texture_name;
// Fill with default value for texopt.
if (is_bump) {
texopt->imfchan = 'l';
} else {
texopt->imfchan = 'm';
}
texopt->bump_multiplier = 1.0f;
texopt->clamp = false;
texopt->blendu = true;
texopt->blendv = true;
texopt->sharpness = 1.0f;
texopt->brightness = 0.0f;
texopt->contrast = 1.0f;
texopt->origin_offset[0] = 0.0f;
texopt->origin_offset[1] = 0.0f;
texopt->origin_offset[2] = 0.0f;
texopt->scale[0] = 1.0f;
texopt->scale[1] = 1.0f;
texopt->scale[2] = 1.0f;
texopt->turbulence[0] = 0.0f;
texopt->turbulence[1] = 0.0f;
texopt->turbulence[2] = 0.0f;
texopt->type = TEXTURE_TYPE_NONE;
const char *token = linebuf; // Assume line ends with NULL
while (!IS_NEW_LINE((*token))) {
if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) {
token += 8;
texopt->blendu = parseOnOff(&token, /* default */ true);
} else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) {
token += 8;
texopt->blendv = parseOnOff(&token, /* default */ true);
} else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) {
token += 7;
texopt->clamp = parseOnOff(&token, /* default */ true);
} else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) {
token += 7;
texopt->sharpness = parseFloat(&token, 1.0);
} else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) {
token += 4;
texopt->bump_multiplier = parseFloat(&token, 1.0);
} else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) {
token += 3;
parseFloat3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]),
&(texopt->origin_offset[2]), &token);
} else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) {
token += 3;
parseFloat3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]),
&token, 1.0, 1.0, 1.0);
} else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) {
token += 3;
parseFloat3(&(texopt->turbulence[0]), &(texopt->turbulence[1]),
&(texopt->turbulence[2]), &token);
} else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) {
token += 5;
texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE);
} else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) {
token += 9;
token += strspn(token, " \t");
const char *end = token + strcspn(token, " \t\r");
if ((end - token) == 1) { // Assume one char for -imfchan
texopt->imfchan = (*token);
}
token = end;
} else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) {
token += 4;
parseFloat2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0);
} else {
// Assume texture filename
token += strspn(token, " \t"); // skip space
size_t len = strcspn(token, " \t\r"); // untile next space
texture_name = std::string(token, token + len);
token += len;
token += strspn(token, " \t"); // skip space
found_texname = true;
}
}
if (found_texname) {
(*texname) = texture_name;
return true;
} else {
return false;
}
}
static void InitMaterial(material_t *material) { static void InitMaterial(material_t *material) {
material->name = ""; material->name = "";
material->ambient_texname = ""; material->ambient_texname = "";
@@ -686,15 +936,26 @@ static bool exportFaceGroupToShape(
return true; return true;
} }
// Split a string with specified delimiter character.
// http://stackoverflow.com/questions/236129/split-a-string-in-c
static void SplitString(const std::string &s, char delim, std::vector<std::string> &elems)
{
std::stringstream ss;
ss.str(s);
std::string item;
while (std::getline(ss, item, delim)) {
elems.push_back(item);
}
}
void LoadMtl(std::map<std::string, int> *material_map, void LoadMtl(std::map<std::string, int> *material_map,
std::vector<material_t> *materials, std::istream *inStream) { std::vector<material_t> *materials, std::istream *inStream) {
// Create a default material anyway. // Create a default material anyway.
material_t material; material_t material;
InitMaterial(&material); InitMaterial(&material);
std::string linebuf;
while (inStream->peek() != -1) { while (inStream->peek() != -1) {
std::string linebuf;
safeGetline(*inStream, linebuf); safeGetline(*inStream, linebuf);
// Trim trailing whitespace. // Trim trailing whitespace.
@@ -892,35 +1153,54 @@ void LoadMtl(std::map<std::string, int> *material_map,
// ambient texture // ambient texture
if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
token += 7; token += 7;
material.ambient_texname = token; ParseTextureNameAndOption(&(material.ambient_texname),
&(material.ambient_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// diffuse texture // diffuse texture
if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) {
token += 7; token += 7;
material.diffuse_texname = token; ParseTextureNameAndOption(&(material.diffuse_texname),
&(material.diffuse_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// specular texture // specular texture
if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) {
token += 7; token += 7;
material.specular_texname = token; ParseTextureNameAndOption(&(material.specular_texname),
&(material.specular_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// specular highlight texture // specular highlight texture
if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) {
token += 7; token += 7;
material.specular_highlight_texname = token; ParseTextureNameAndOption(&(material.specular_highlight_texname),
&(material.specular_highlight_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// bump texture // bump texture
if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) {
token += 9; token += 9;
material.bump_texname = token; ParseTextureNameAndOption(&(material.bump_texname),
&(material.bump_texopt), token,
/* is_bump */ true);
continue;
}
// bump texture
if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
token += 5;
ParseTextureNameAndOption(&(material.bump_texname),
&(material.bump_texopt), token,
/* is_bump */ true);
continue; continue;
} }
@@ -928,55 +1208,63 @@ void LoadMtl(std::map<std::string, int> *material_map,
if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) {
token += 6; token += 6;
material.alpha_texname = token; material.alpha_texname = token;
continue; ParseTextureNameAndOption(&(material.alpha_texname),
} &(material.alpha_texopt), token,
/* is_bump */ false);
// bump texture
if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) {
token += 5;
material.bump_texname = token;
continue; continue;
} }
// displacement texture // displacement texture
if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) {
token += 5; token += 5;
material.displacement_texname = token; ParseTextureNameAndOption(&(material.displacement_texname),
&(material.displacement_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// PBR: roughness texture // PBR: roughness texture
if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
token += 7; token += 7;
material.roughness_texname = token; ParseTextureNameAndOption(&(material.roughness_texname),
&(material.roughness_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// PBR: metallic texture // PBR: metallic texture
if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
token += 7; token += 7;
material.metallic_texname = token; ParseTextureNameAndOption(&(material.metallic_texname),
&(material.metallic_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// PBR: sheen texture // PBR: sheen texture
if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
token += 7; token += 7;
material.sheen_texname = token; ParseTextureNameAndOption(&(material.sheen_texname),
&(material.sheen_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// PBR: emissive texture // PBR: emissive texture
if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
token += 7; token += 7;
material.emissive_texname = token; ParseTextureNameAndOption(&(material.emissive_texname),
&(material.emissive_texopt), token,
/* is_bump */ false);
continue; continue;
} }
// PBR: normal map texture // PBR: normal map texture
if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
token += 5; token += 5;
material.normal_texname = token; ParseTextureNameAndOption(
&(material.normal_texname), &(material.normal_texopt), token,
/* is_bump */ false); // @fixme { is_bump will be true? }
continue; continue;
} }
@@ -1005,22 +1293,24 @@ bool MaterialFileReader::operator()(const std::string &matId,
std::string *err) { std::string *err) {
std::string filepath; std::string filepath;
if (!m_mtlBasePath.empty()) { if (!m_mtlBaseDir.empty()) {
filepath = std::string(m_mtlBasePath) + matId; filepath = std::string(m_mtlBaseDir) + matId;
} else { } else {
filepath = matId; filepath = matId;
} }
std::ifstream matIStream(filepath.c_str()); std::ifstream matIStream(filepath.c_str());
LoadMtl(matMap, materials, &matIStream);
if (!matIStream) { if (!matIStream) {
std::stringstream ss; std::stringstream ss;
ss << "WARN: Material file [ " << filepath ss << "WARN: Material file [ " << filepath
<< " ] not found. Created a default material."; << " ] not found." << std::endl;
if (err) { if (err) {
(*err) += ss.str(); (*err) += ss.str();
} }
return false;
} }
LoadMtl(matMap, materials, &matIStream);
return true; return true;
} }
@@ -1028,21 +1318,23 @@ bool MaterialStreamReader::operator()(const std::string &matId,
std::vector<material_t> *materials, std::vector<material_t> *materials,
std::map<std::string, int> *matMap, std::map<std::string, int> *matMap,
std::string *err) { std::string *err) {
LoadMtl(matMap, materials, &m_inStream); (void)matId;
if (!m_inStream) { if (!m_inStream) {
std::stringstream ss; std::stringstream ss;
ss << "WARN: Material stream in error state." ss << "WARN: Material stream in error state. " << std::endl;
<< " Created a default material.";
if (err) { if (err) {
(*err) += ss.str(); (*err) += ss.str();
} }
return false;
} }
LoadMtl(matMap, materials, &m_inStream);
return true; return true;
} }
bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
std::vector<material_t> *materials, std::string *err, std::vector<material_t> *materials, std::string *err,
const char *filename, const char *mtl_basepath, const char *filename, const char *mtl_basedir,
bool trianglulate) { bool trianglulate) {
attrib->vertices.clear(); attrib->vertices.clear();
attrib->normals.clear(); attrib->normals.clear();
@@ -1060,11 +1352,11 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
return false; return false;
} }
std::string basePath; std::string baseDir;
if (mtl_basepath) { if (mtl_basedir) {
basePath = mtl_basepath; baseDir = mtl_basedir;
} }
MaterialFileReader matFileReader(basePath); MaterialFileReader matFileReader(baseDir);
return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader, return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader,
trianglulate); trianglulate);
@@ -1072,8 +1364,7 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes, bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
std::vector<material_t> *materials, std::string *err, std::vector<material_t> *materials, std::string *err,
std::istream *inStream, std::istream *inStream, MaterialReader *readMatFn /*= NULL*/,
MaterialReader *readMatFn /*= NULL*/,
bool triangulate) { bool triangulate) {
std::stringstream errss; std::stringstream errss;
@@ -1090,8 +1381,8 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
shape_t shape; shape_t shape;
std::string linebuf;
while (inStream->peek() != -1) { while (inStream->peek() != -1) {
std::string linebuf;
safeGetline(*inStream, linebuf); safeGetline(*inStream, linebuf);
// Trim newline '\r\n' or '\n' // Trim newline '\r\n' or '\n'
@@ -1207,23 +1498,37 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
// load mtl // load mtl
if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
if (readMatFn) { if (readMatFn) {
char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
token += 7; token += 7;
#ifdef _MSC_VER
sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
#else
sscanf(token, "%s", namebuf);
#endif
std::string err_mtl; std::vector<std::string> filenames;
bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl); SplitString(std::string(token), ' ', filenames);
if (err) {
(*err) += err_mtl;
}
if (!ok) { if (filenames.empty()) {
faceGroup.clear(); // for safety if (err) {
return false; (*err) += "WARN: Looks like empty filename for mtllib. Use default material. \n";
}
} else {
bool found = false;
for (size_t s = 0; s < filenames.size(); s++) {
std::string err_mtl;
bool ok = (*readMatFn)(filenames[s].c_str(), materials, &material_map, &err_mtl);
if (err && (!err_mtl.empty())) {
(*err) += err_mtl; // This should be warn message.
}
if (ok) {
found = true;
break;
}
}
if (!found) {
if (err) {
(*err) += "WARN: Failed to load material file(s). Use default material.\n";
}
}
} }
} }
@@ -1499,28 +1804,44 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
// load mtl // load mtl
if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
if (readMatFn) { if (readMatFn) {
char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
token += 7; token += 7;
#ifdef _MSC_VER token += 7;
sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
#else
sscanf(token, "%s", namebuf);
#endif
std::string err_mtl; std::vector<std::string> filenames;
materials.clear(); SplitString(std::string(token), ' ', filenames);
bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl);
if (err) {
(*err) += err_mtl;
}
if (!ok) { if (filenames.empty()) {
return false; if (err) {
} (*err) += "WARN: Looks like empty filename for mtllib. Use default material. \n";
}
} else {
if (callback.mtllib_cb) { bool found = false;
callback.mtllib_cb(user_data, &materials.at(0), for (size_t s = 0; s < filenames.size(); s++) {
static_cast<int>(materials.size()));
std::string err_mtl;
bool ok = (*readMatFn)(filenames[s].c_str(), &materials, &material_map, &err_mtl);
if (err && (!err_mtl.empty())) {
(*err) += err_mtl; // This should be warn message.
}
if (ok) {
found = true;
break;
}
}
if (!found) {
if (err) {
(*err) += "WARN: Failed to load material file(s). Use default material.\n";
}
} else {
if (callback.mtllib_cb) {
callback.mtllib_cb(user_data, &materials.at(0),
static_cast<int>(materials.size()));
}
}
} }
} }