12 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
10 changed files with 588 additions and 118 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;
} }
@@ -223,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);
@@ -395,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);
@@ -411,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;
@@ -440,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) {
@@ -467,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;
@@ -494,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);
@@ -513,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));

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,6 +233,7 @@ 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");

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,7 +23,10 @@ THE SOFTWARE.
*/ */
// //
// version 1.0.2 : Improve parsing speed by about a factor of 2 for large files(#105) // 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.
// //
@@ -43,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;
@@ -67,22 +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 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;
@@ -171,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 {
@@ -200,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.
@@ -421,20 +518,13 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) {
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[] = { static const double pow_lut[] = {
1.0, 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001,
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]; 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) * mantissa += static_cast<int>(*curr - 0x30) *
(read < lut_entries ? pow_lut[read] : pow(10.0, -read)); (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);
@@ -474,8 +564,9 @@ static bool tryParseDouble(const char *s, const char *s_end, double *result) {
} }
assemble: assemble:
*result = (sign == '+' ? 1 : -1) * *result =
(exponent ? ldexp(mantissa * pow(5.0, exponent), exponent) : mantissa); (sign == '+' ? 1 : -1) *
(exponent ? ldexp(mantissa * pow(5.0, exponent), exponent) : mantissa);
return true; return true;
fail: fail:
return false; return false;
@@ -491,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) {
@@ -601,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 = "";
@@ -701,6 +936,18 @@ 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.
@@ -906,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;
} }
@@ -942,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;
} }
@@ -1019,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;
} }
@@ -1043,21 +1319,22 @@ bool MaterialStreamReader::operator()(const std::string &matId,
std::map<std::string, int> *matMap, std::map<std::string, int> *matMap,
std::string *err) { std::string *err) {
(void)matId; (void)matId;
LoadMtl(matMap, materials, &m_inStream);
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();
@@ -1075,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);
@@ -1087,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;
@@ -1222,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";
}
}
} }
} }
@@ -1514,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()));
}
}
} }
} }