24 Commits

Author SHA1 Message Date
Syoyo Fujita
fed4322d26 Merge branch 'master' of github.com:syoyo/tinyobjloader 2016-10-18 17:34:15 +09:00
Syoyo Fujita
039d4a6c54 Fix a shape is lost if obj ends with a 'usemtl'. Fixes #104 2016-10-18 17:33:28 +09:00
Syoyo Fujita
28ee1b42ce Merge pull request #103 from chrisliebert/master
Issue #74: Added basic diffuse texture support to examples/viewer
2016-10-08 14:02:11 +09:00
Chris Liebert
35889026d6 added additional bounds checking 2016-10-07 21:21:33 -07:00
Chris Liebert
e81ac971b0 Issue #74: Added basic diffuse texture support to examples/viewer using stb_image. The previously used color based on the normal is combined with the diffuse color from the material definition. 2016-10-07 20:58:04 -07:00
Syoyo Fujita
e05ce6aff0 Merge pull request #102 from Entalpi/master
Minor spelling fix
2016-10-07 11:40:33 +09:00
Alexander Lingtorp
e4598ba84a Minor spelling fix 2016-10-06 19:30:21 +02:00
Syoyo Fujita
79513077f3 Fix compilation on pre-C++11 compiler. 2016-10-02 17:37:02 +09:00
Syoyo Fujita
72acadca79 Merge pull request #101 from kavika13/master
Make it easier to use an existing stream reader
2016-10-02 17:35:36 +09:00
Merlyn Morgan-Graham
398e358826 Add unit tests for reading from existing file stream 2016-10-02 01:25:07 -07:00
Merlyn Morgan-Graham
7fc9b0fe97 Add stream based material reader implementation 2016-10-02 00:52:45 -07:00
Merlyn Morgan-Graham
71cc967f42 Allow skipping material reads on LoadObj istream overload 2016-10-02 00:52:19 -07:00
Syoyo Fujita
3ddad1e377 Merge pull request #97 from middlefeng/url-update
Metal viewer URL update.
2016-09-09 00:45:08 +09:00
middleware
92805d3088 Metal viewer URL update. 2016-09-07 06:31:08 -07:00
Syoyo Fujita
60acd05a03 Add brief README. 2016-09-06 02:18:24 +09:00
Syoyo Fujita
4a18e241d9 Measure zstd decompress time. 2016-09-06 02:11:23 +09:00
Syoyo Fujita
1b88a1e3c7 Support reading .obj from ZStd compressed format. 2016-09-04 19:34:05 +09:00
Syoyo Fujita
b3feefafdf Add link to MetalExamples. 2016-09-03 21:43:00 +09:00
Syoyo Fujita
a7a16db908 Add a link to GeeXLab. 2016-08-23 02:48:06 +09:00
Syoyo Fujita
9f92ba34a6 Update Feature list. 2016-08-23 02:44:32 +09:00
Syoyo Fujita
25cf039953 Add more link. 2016-08-23 02:37:47 +09:00
Syoyo Fujita
6bf145d7cf Add link to "Fast OBJ file importing and parsing in CUDA". 2016-08-23 02:35:31 +09:00
Syoyo Fujita
69240e18b4 Add link to PBGI. 2016-08-23 02:29:41 +09:00
Syoyo Fujita
5b9f431512 Update TODO list. 2016-08-20 18:16:50 +09:00
9 changed files with 7150 additions and 33 deletions

View File

@@ -52,6 +52,7 @@ TinyObjLoader is successfully used in ...
### New version(v1.0.x) ### New version(v1.0.x)
* Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models * Loading models in Vulkan Tutorial https://vulkan-tutorial.com/Loading_models
* .obj viewer with Metal https://github.com/middlefeng/NuoModelViewer/tree/master
* Your project here! * Your project here!
### Old version(v0.9.x) ### Old version(v0.9.x)
@@ -73,6 +74,11 @@ TinyObjLoader is successfully used in ...
* FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK * FireRays SDK https://github.com/GPUOpen-LibrariesAndSDKs/FireRays_SDK
* parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg * parg, tiny C library of various graphics utilities and GL demos https://github.com/prideout/parg
* Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl * Opengl unit of ChronoEngine https://github.com/projectchrono/chrono-opengl
* Point Based Global Illumination on modern GPU https://pbgi.wordpress.com/code-source/
* Fast OBJ file importing and parsing in CUDA http://researchonline.jcu.edu.au/42515/1/2015.CVM.OBJCUDA.pdf
* Sorted Shading for Uni-Directional Pathtracing by Joshua Bainbridge https://nccastaff.bournemouth.ac.uk/jmacey/MastersProjects/MSc15/02Josh/joshua_bainbridge_thesis.pdf
* GeeXLab http://www.geeks3d.com/hacklab/20160531/geexlab-0-12-0-0-released-for-windows/
## Features ## Features
@@ -83,6 +89,7 @@ TinyObjLoader is successfully used in ...
* Material * Material
* Unknown material attributes are returned as key-value(value is string) map. * Unknown material attributes are returned as key-value(value is string) map.
* Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification) * Crease tag('t'). This is OpenSubdiv specific(not in wavefront .obj specification)
* PBR material extension for .MTL. Its proposed here: http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
* Callback API for custom loading. * Callback API for custom loading.
@@ -90,6 +97,9 @@ TinyObjLoader is successfully used in ...
* [ ] Fix obj_sticker example. * [ ] Fix obj_sticker example.
* [ ] More unit test codes. * [ ] More unit test codes.
* [ ] Texture options
* [ ] Normal vector generation
* [ ] Support smoothing groups
## License ## License

6755
examples/viewer/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,7 @@
#include <cstdlib> #include <cstdlib>
#include <iostream> #include <iostream>
#include <limits> #include <limits>
#include <map>
#include <string> #include <string>
#include <vector> #include <vector>
@@ -26,12 +27,24 @@
#include "trackball.h" #include "trackball.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#ifdef _WIN32 #ifdef _WIN32
#ifdef __cplusplus #ifdef __cplusplus
extern "C" { extern "C" {
#endif #endif
#include <mmsystem.h>
#include <windows.h> #include <windows.h>
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#include <mmsystem.h>
#ifdef __cplusplus #ifdef __cplusplus
} }
#endif #endif
@@ -112,6 +125,7 @@ class timerutil {
typedef struct { typedef struct {
GLuint vb; // vertex buffer GLuint vb; // vertex buffer
int numTriangles; int numTriangles;
size_t material_id;
} DrawObject; } DrawObject;
std::vector<DrawObject> gDrawObjects; std::vector<DrawObject> gDrawObjects;
@@ -163,10 +177,11 @@ void CalcNormal(float N[3], float v0[3], float v1[3], float v2[3]) {
bool LoadObjAndConvert(float bmin[3], float bmax[3], bool LoadObjAndConvert(float bmin[3], float bmax[3],
std::vector<DrawObject>* drawObjects, std::vector<DrawObject>* drawObjects,
std::vector<tinyobj::material_t>& materials,
std::map<std::string, GLuint>& textures,
const char* filename) { const char* filename) {
tinyobj::attrib_t attrib; tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes; std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
timerutil tm; timerutil tm;
@@ -194,11 +209,50 @@ 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());
// Load diffuse textures
{
for (size_t m = 0; m < materials.size(); m++) {
tinyobj::material_t* mp = &materials[m];
if (mp->diffuse_texname.length() > 0) {
// Only load the texture if it is not already loaded
if (textures.find(mp->diffuse_texname) == textures.end()) {
GLuint texture_id;
int w, h;
int comp;
unsigned char* image = stbi_load(mp->diffuse_texname.c_str(), &w, &h, &comp, STBI_default);
if (image == nullptr) {
std::cerr << "Unable to load texture: " << mp->diffuse_texname << std::endl;
exit(1);
}
glGenTextures(1, &texture_id);
glBindTexture(GL_TEXTURE_2D, texture_id);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
if (comp == 3) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGB, w, h, 0, GL_RGB, GL_UNSIGNED_BYTE, image);
}
else if (comp == 4) {
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, w, h, 0, GL_RGBA, GL_UNSIGNED_BYTE, image);
}
glBindTexture(GL_TEXTURE_2D, 0);
stbi_image_free(image);
textures.insert(std::make_pair(mp->diffuse_texname, texture_id));
}
}
}
}
bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max(); bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max(); bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
{ {
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++) {
@@ -206,6 +260,32 @@ 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];
if (current_material_id >= materials.size()) {
std::cerr << "Invalid material index: " << current_material_id << std::endl;
}
float diffuse[3];
for (size_t i = 0; i < 3; i++) {
diffuse[i] = materials[current_material_id].diffuse[i];
}
float tc[3][2];
if (attrib.texcoords.size() > 0) {
assert(attrib.texcoords.size() > 2 * idx0.texcoord_index + 1);
assert(attrib.texcoords.size() > 2 * idx1.texcoord_index + 1);
assert(attrib.texcoords.size() > 2 * idx2.texcoord_index + 1);
tc[0][0] = attrib.texcoords[2 * idx0.texcoord_index];
tc[0][1] = 1.0f - attrib.texcoords[2 * idx0.texcoord_index + 1];
tc[1][0] = attrib.texcoords[2 * idx1.texcoord_index];
tc[1][1] = 1.0f - attrib.texcoords[2 * idx1.texcoord_index + 1];
tc[2][0] = attrib.texcoords[2 * idx2.texcoord_index];
tc[2][1] = 1.0f - attrib.texcoords[2 * idx2.texcoord_index + 1];
} else {
std::cerr << "Texcoordinates are not defined" << std::endl;
exit(2);
}
float v[3][3]; float v[3][3];
for (int k = 0; k < 3; k++) { for (int k = 0; k < 3; k++) {
int f0 = idx0.vertex_index; int f0 = idx0.vertex_index;
@@ -227,7 +307,6 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
} }
float n[3][3]; float n[3][3];
if (attrib.normals.size() > 0) { if (attrib.normals.size() > 0) {
int f0 = idx0.normal_index; int f0 = idx0.normal_index;
int f1 = idx1.normal_index; int f1 = idx1.normal_index;
@@ -258,8 +337,14 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
vb.push_back(n[k][0]); vb.push_back(n[k][0]);
vb.push_back(n[k][1]); vb.push_back(n[k][1]);
vb.push_back(n[k][2]); vb.push_back(n[k][2]);
// Use normal as color. // Combine normal and diffuse to get color.
float c[3] = {n[k][0], n[k][1], n[k][2]}; float normal_factor = 0.2;
float diffuse_factor = 1 - normal_factor;
float c[3] = {
n[k][0] * normal_factor + diffuse[0] * diffuse_factor,
n[k][1] * normal_factor + diffuse[1] * diffuse_factor,
n[k][2] * normal_factor + diffuse[2] * diffuse_factor
};
float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2]; float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
if (len2 > 0.0f) { if (len2 > 0.0f) {
float len = sqrtf(len2); float len = sqrtf(len2);
@@ -271,17 +356,21 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
vb.push_back(c[0] * 0.5 + 0.5); vb.push_back(c[0] * 0.5 + 0.5);
vb.push_back(c[1] * 0.5 + 0.5); vb.push_back(c[1] * 0.5 + 0.5);
vb.push_back(c[2] * 0.5 + 0.5); vb.push_back(c[2] * 0.5 + 0.5);
vb.push_back(tc[k][0]);
vb.push_back(tc[k][1]);
} }
} }
o.vb = 0; o.vb = 0;
o.numTriangles = 0; o.numTriangles = 0;
o.material_id = current_material_id;
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);
glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0), glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0),
GL_STATIC_DRAW); GL_STATIC_DRAW);
o.numTriangles = vb.size() / 9 / 3; o.numTriangles = vb.size() / (3 + 3 + 3 + 2) * 3;
printf("shape[%d] # of triangles = %d\n", static_cast<int>(s), printf("shape[%d] # of triangles = %d\n", static_cast<int>(s),
o.numTriangles); o.numTriangles);
} }
@@ -395,13 +484,13 @@ void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) {
prevMouseY = mouse_y; prevMouseY = mouse_y;
} }
void Draw(const std::vector<DrawObject>& drawObjects) { 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);
glEnable(GL_POLYGON_OFFSET_FILL); glEnable(GL_POLYGON_OFFSET_FILL);
glPolygonOffset(1.0, 1.0); glPolygonOffset(1.0, 1.0);
glColor3f(1.0f, 1.0f, 1.0f); GLsizei stride = (3 + 3 + 3 + 2) * sizeof(float);
for (size_t i = 0; i < drawObjects.size(); i++) { for (size_t i = 0; i < drawObjects.size(); i++) {
DrawObject o = drawObjects[i]; DrawObject o = drawObjects[i];
if (o.vb < 1) { if (o.vb < 1) {
@@ -412,12 +501,20 @@ void Draw(const std::vector<DrawObject>& drawObjects) {
glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_NORMAL_ARRAY);
glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 36, (const void*)0); glEnableClientState(GL_TEXTURE_COORD_ARRAY);
glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float) * 3));
glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float) * 6)); std::string diffuse_texname = materials[o.material_id].diffuse_texname;
if (diffuse_texname.length() > 0) {
glBindTexture(GL_TEXTURE_2D, textures[diffuse_texname]);
}
glVertexPointer(3, GL_FLOAT, stride, (const void*)0);
glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3));
glColorPointer(3, GL_FLOAT, stride, (const void*)(sizeof(float) * 6));
glTexCoordPointer(2, GL_FLOAT, stride, (const void*)(sizeof(float) * 9));
glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles);
CheckErrors("drawarrays"); CheckErrors("drawarrays");
glBindTexture(GL_TEXTURE_2D, 0);
} }
// draw wireframe // draw wireframe
@@ -436,8 +533,11 @@ void Draw(const std::vector<DrawObject>& drawObjects) {
glEnableClientState(GL_VERTEX_ARRAY); glEnableClientState(GL_VERTEX_ARRAY);
glEnableClientState(GL_NORMAL_ARRAY); glEnableClientState(GL_NORMAL_ARRAY);
glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_COLOR_ARRAY);
glVertexPointer(3, GL_FLOAT, 36, (const void*)0); glDisableClientState(GL_TEXTURE_COORD_ARRAY);
glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float) * 3)); glVertexPointer(3, GL_FLOAT, stride, (const void*)0);
glNormalPointer(GL_FLOAT, stride, (const void*)(sizeof(float) * 3));
glColorPointer(3, GL_FLOAT, stride, (const void*)(sizeof(float) * 6));
glTexCoordPointer(2, GL_FLOAT, stride, (const void*)(sizeof(float) * 9));
glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles); glDrawArrays(GL_TRIANGLES, 0, 3 * o.numTriangles);
CheckErrors("drawarrays"); CheckErrors("drawarrays");
@@ -498,7 +598,9 @@ int main(int argc, char** argv) {
reshapeFunc(window, width, height); reshapeFunc(window, width, height);
float bmin[3], bmax[3]; float bmin[3], bmax[3];
if (false == LoadObjAndConvert(bmin, bmax, &gDrawObjects, argv[1])) { std::vector<tinyobj::material_t> materials;
std::map<std::string, GLuint> textures;
if (false == LoadObjAndConvert(bmin, bmax, &gDrawObjects, materials, textures, argv[1])) {
return -1; return -1;
} }
@@ -516,6 +618,7 @@ int main(int argc, char** argv) {
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST); glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
// camera & rotate // camera & rotate
glMatrixMode(GL_MODELVIEW); glMatrixMode(GL_MODELVIEW);
@@ -533,7 +636,7 @@ int main(int argc, char** argv) {
glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]), glTranslatef(-0.5 * (bmax[0] + bmin[0]), -0.5 * (bmax[1] + bmin[1]),
-0.5 * (bmax[2] + bmin[2])); -0.5 * (bmax[2] + bmin[2]));
Draw(gDrawObjects); Draw(gDrawObjects, materials, textures);
glfwSwapBuffers(window); glfwSwapBuffers(window);
} }

5
experimental/README.md Normal file
View File

@@ -0,0 +1,5 @@
Experimental code for .obj loader.
* Multi-threaded optimized parser : tinyobj_loader_opt.h
* zstd compressed .obj support. `--with-zstd` premake option.
* gzip compressed .obj support. `--with-zlib` premake option.

View File

@@ -3,6 +3,11 @@ newoption {
description = "Build with zlib." description = "Build with zlib."
} }
newoption {
trigger = "with-zstd",
description = "Build with ZStandard compression."
}
solution "objview" solution "objview"
-- location ( "build" ) -- location ( "build" )
configurations { "Release", "Debug" } configurations { "Release", "Debug" }
@@ -23,6 +28,15 @@ solution "objview"
links { 'z' } links { 'z' }
end end
if _OPTIONS['with-zstd'] then
print("with-zstd")
defines { 'ENABLE_ZSTD' }
-- Set path to zstd installed dir.
includedirs { '$$HOME/local/include' }
libdirs { '$$HOME/local/lib' }
links { 'zstd' }
end
-- Uncomment if you want address sanitizer(gcc/clang only) -- Uncomment if you want address sanitizer(gcc/clang only)
--buildoptions { "-fsanitize=address" } --buildoptions { "-fsanitize=address" }
--linkoptions { "-fsanitize=address" } --linkoptions { "-fsanitize=address" }

View File

@@ -16,6 +16,10 @@
#include <zlib.h> #include <zlib.h>
#endif #endif
#if defined(ENABLE_ZSTD)
#include <zstd.h>
#endif
#include <GL/glew.h> #include <GL/glew.h>
#ifdef __APPLE__ #ifdef __APPLE__
@@ -182,6 +186,71 @@ bool gz_load(std::vector<char>* buf, const char* filename)
#endif #endif
} }
#ifdef ENABLE_ZSTD
static off_t fsize_X(const char *filename)
{
struct stat st;
if (stat(filename, &st) == 0) return st.st_size;
/* error */
printf("stat: %s : %s \n", filename, strerror(errno));
exit(1);
}
static FILE* fopen_X(const char *filename, const char *instruction)
{
FILE* const inFile = fopen(filename, instruction);
if (inFile) return inFile;
/* error */
printf("fopen: %s : %s \n", filename, strerror(errno));
exit(2);
}
static void* malloc_X(size_t size)
{
void* const buff = malloc(size);
if (buff) return buff;
/* error */
printf("malloc: %s \n", strerror(errno));
exit(3);
}
#endif
bool zstd_load(std::vector<char>* buf, const char* filename)
{
#ifdef ENABLE_ZSTD
off_t const buffSize = fsize_X(filename);
FILE* const inFile = fopen_X(filename, "rb");
void* const buffer = malloc_X(buffSize);
size_t const readSize = fread(buffer, 1, buffSize, inFile);
if (readSize != (size_t)buffSize) {
printf("fread: %s : %s \n", filename, strerror(errno));
exit(4);
}
fclose(inFile);
unsigned long long const rSize = ZSTD_getDecompressedSize(buffer, buffSize);
if (rSize==0) {
printf("%s : original size unknown \n", filename);
exit(5);
}
buf->resize(rSize);
size_t const dSize = ZSTD_decompress(buf->data(), rSize, buffer, buffSize);
if (dSize != rSize) {
printf("error decoding %s : %s \n", filename, ZSTD_getErrorName(dSize));
exit(7);
}
free(buffer);
return true;
#else
return false;
#endif
}
const char* get_file_data(size_t *len, const char* filename) const char* get_file_data(size_t *len, const char* filename)
{ {
@@ -204,6 +273,20 @@ const char* get_file_data(size_t *len, const char* filename)
data_len = buf.size(); data_len = buf.size();
} }
} else if (strcmp(ext, ".zst") == 0) {
printf("zstd\n");
// Zstandard data.
std::vector<char> buf;
bool ret = zstd_load(&buf, filename);
if (ret) {
char *p = static_cast<char*>(malloc(buf.size() + 1)); // @fixme { implement deleter }
memcpy(p, &buf.at(0), buf.size());
p[buf.size()] = '\0';
data = p;
data_len = buf.size();
}
} else { } else {
data = mmap_file(&data_len, filename); data = mmap_file(&data_len, filename);
@@ -220,13 +303,22 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n
std::vector<tinyobj_opt::shape_t> shapes; std::vector<tinyobj_opt::shape_t> shapes;
std::vector<tinyobj_opt::material_t> materials; std::vector<tinyobj_opt::material_t> materials;
auto load_t_begin = std::chrono::high_resolution_clock::now();
size_t data_len = 0; size_t data_len = 0;
const char* data = get_file_data(&data_len, filename); const char* data = get_file_data(&data_len, filename);
if (data == nullptr) { if (data == nullptr) {
printf("failed to load file\n");
exit(-1); exit(-1);
return false; return false;
} }
printf("filesize: %d\n", (int)data_len); auto load_t_end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double, std::milli> load_ms = load_t_end - load_t_begin;
if (verbose) {
std::cout << "filesize: " << data_len << std::endl;
std::cout << "load time: " << load_ms.count() << " [msecs]" << std::endl;
}
tinyobj_opt::LoadOption option; tinyobj_opt::LoadOption option;
option.req_num_threads = num_threads; option.req_num_threads = num_threads;
option.verbose = verbose; option.verbose = verbose;
@@ -534,6 +626,7 @@ int main(int argc, char **argv)
size_t data_len = 0; size_t data_len = 0;
const char* data = get_file_data(&data_len, argv[1]); const char* data = get_file_data(&data_len, argv[1]);
if (data == nullptr) { if (data == nullptr) {
printf("failed to load file\n");
exit(-1); exit(-1);
return false; return false;
} }

View File

@@ -0,0 +1,30 @@
# cornell_box.obj and cornell_box.mtl are grabbed from Intel's embree project.
# original cornell box data
# comment
# empty line including some space
mtllib cornell_box.mtl
o floor
v 552.8 0.0 0.0
v 0.0 0.0 0.0
v 0.0 0.0 559.2
v 549.6 0.0 559.2
v 130.0 0.0 65.0
v 82.0 0.0 225.0
v 240.0 0.0 272.0
v 290.0 0.0 114.0
v 423.0 0.0 247.0
v 265.0 0.0 296.0
v 314.0 0.0 456.0
v 472.0 0.0 406.0
f 1 2 3 4
f 8 7 6 5
f 12 11 10 9
usemtl white

View File

@@ -184,6 +184,48 @@ TestLoadObj(
return true; return true;
} }
static bool
TestLoadObjFromPreopenedFile(
const char* filename,
const char* basepath = NULL,
bool readMaterials = true,
bool triangulate = true)
{
std::string fullFilename = std::string(basepath) + filename;
std::cout << "Loading " << fullFilename << std::endl;
std::ifstream fileStream(fullFilename.c_str());
if (!fileStream) {
std::cerr << "Could not find specified file: " << fullFilename << std::endl;
return false;
}
tinyobj::MaterialStreamReader materialStreamReader(fileStream);
tinyobj::MaterialStreamReader* materialReader = readMaterials
? &materialStreamReader
: NULL;
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, &fileStream, materialReader);
if (!err.empty()) {
std::cerr << err << std::endl;
}
if (!ret) {
printf("Failed to load/parse .obj.\n");
return false;
}
std::cout << "Loaded material count: " << materials.size() << "\n";
return true;
}
static bool static bool
TestStreamLoadObj() TestStreamLoadObj()
@@ -351,6 +393,16 @@ TEST_CASE("stream_load", "[Stream]") {
REQUIRE(true == TestStreamLoadObj()); REQUIRE(true == TestStreamLoadObj());
} }
TEST_CASE("stream_load_from_file_skipping_materials", "[Stream]") {
REQUIRE(true == TestLoadObjFromPreopenedFile(
"../models/pbr-mat-ext.obj", gMtlBasePath, /*readMaterials*/false, /*triangulate*/false));
}
TEST_CASE("stream_load_from_file_with_materials", "[Stream]") {
REQUIRE(true == TestLoadObjFromPreopenedFile(
"../models/pbr-mat-ext.obj", gMtlBasePath, /*readMaterials*/true, /*triangulate*/false));
}
TEST_CASE("trailing_whitespace_in_mtl", "[Issue92]") { TEST_CASE("trailing_whitespace_in_mtl", "[Issue92]") {
tinyobj::attrib_t attrib; tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes; std::vector<tinyobj::shape_t> shapes;
@@ -402,6 +454,7 @@ TEST_CASE("transmittance_filter_Tf", "[Issue95-Tf]") {
REQUIRE(0.2 == Approx(materials[0].transmittance[1])); REQUIRE(0.2 == Approx(materials[0].transmittance[1]));
REQUIRE(0.3 == Approx(materials[0].transmittance[2])); REQUIRE(0.3 == Approx(materials[0].transmittance[2]));
} }
TEST_CASE("transmittance_filter_Kt", "[Issue95-Kt]") { TEST_CASE("transmittance_filter_Kt", "[Issue95-Kt]") {
tinyobj::attrib_t attrib; tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes; std::vector<tinyobj::shape_t> shapes;
@@ -420,6 +473,21 @@ TEST_CASE("transmittance_filter_Kt", "[Issue95-Kt]") {
REQUIRE(0.3 == Approx(materials[0].transmittance[2])); REQUIRE(0.3 == Approx(materials[0].transmittance[2]));
} }
TEST_CASE("usemtl_at_last_line", "[Issue104]") {
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/usemtl-issue-104.obj", gMtlBasePath);
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(1 == shapes.size());
}
#if 0 #if 0
int int
main( main(

View File

@@ -23,6 +23,7 @@ THE SOFTWARE.
*/ */
// //
// 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.
// //
@@ -91,7 +92,7 @@ typedef struct {
std::vector<std::string> stringValues; std::vector<std::string> stringValues;
} tag_t; } tag_t;
// Index struct to support differnt indices for vtx/normal/texcoord. // Index struct to support different indices for vtx/normal/texcoord.
// -1 means not used. // -1 means not used.
typedef struct { typedef struct {
int vertex_index; int vertex_index;
@@ -179,6 +180,19 @@ class MaterialFileReader : public MaterialReader {
std::string m_mtlBasePath; std::string m_mtlBasePath;
}; };
class MaterialStreamReader : public MaterialReader {
public:
explicit MaterialStreamReader(std::istream &inStream)
: m_inStream(inStream) {}
virtual ~MaterialStreamReader() {}
virtual bool operator()(const std::string &matId,
std::vector<material_t> *materials,
std::map<std::string, int> *matMap, std::string *err);
private:
std::istream &m_inStream;
};
/// Loads .obj from a file. /// Loads .obj from a file.
/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data /// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data
/// 'shapes' will be filled with parsed shape data /// 'shapes' will be filled with parsed shape data
@@ -209,7 +223,7 @@ bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
/// Returns warning and error message into `err` /// Returns warning and error message into `err`
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, MaterialReader *readMatFn, std::istream *inStream, MaterialReader *readMatFn = NULL,
bool triangulate = true); bool triangulate = true);
/// Loads materials into std::map /// Loads materials into std::map
@@ -1010,6 +1024,22 @@ bool MaterialFileReader::operator()(const std::string &matId,
return true; return true;
} }
bool MaterialStreamReader::operator()(const std::string &matId,
std::vector<material_t> *materials,
std::map<std::string, int> *matMap,
std::string *err) {
LoadMtl(matMap, materials, &m_inStream);
if (!m_inStream) {
std::stringstream ss;
ss << "WARN: Material stream in error state."
<< " Created a default material.";
if (err) {
(*err) += ss.str();
}
}
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_basepath,
@@ -1042,7 +1072,8 @@ 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, MaterialReader *readMatFn, std::istream *inStream,
MaterialReader *readMatFn /*= NULL*/,
bool triangulate) { bool triangulate) {
std::stringstream errss; std::stringstream errss;
@@ -1161,7 +1192,9 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
} }
if (newMaterialId != material) { if (newMaterialId != material) {
// Create per-face material // Create per-face material. Thus we don't add `shape` to `shapes` at
// this time.
// just clear `faceGroup` after `exportFaceGroupToShape()` call.
exportFaceGroupToShape(&shape, faceGroup, tags, material, name, exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
triangulate); triangulate);
faceGroup.clear(); faceGroup.clear();
@@ -1173,23 +1206,25 @@ 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]))) {
char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; if (readMatFn) {
token += 7; char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
token += 7;
#ifdef _MSC_VER #ifdef _MSC_VER
sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
#else #else
sscanf(token, "%s", namebuf); sscanf(token, "%s", namebuf);
#endif #endif
std::string err_mtl; std::string err_mtl;
bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl); bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl);
if (err) { if (err) {
(*err) += err_mtl; (*err) += err_mtl;
} }
if (!ok) { if (!ok) {
faceGroup.clear(); // for safety faceGroup.clear(); // for safety
return false; return false;
}
} }
continue; continue;
@@ -1307,7 +1342,11 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
triangulate); triangulate);
if (ret) { // exportFaceGroupToShape return false when `usemtl` is called in the last
// line.
// we also add `shape` to `shapes` when `shape.mesh` has already some
// faces(indices)
if (ret || shape.mesh.indices.size()) {
shapes->push_back(shape); shapes->push_back(shape);
} }
faceGroup.clear(); // for safety faceGroup.clear(); // for safety