Compare commits
24 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fed4322d26 | ||
|
|
039d4a6c54 | ||
|
|
28ee1b42ce | ||
|
|
35889026d6 | ||
|
|
e81ac971b0 | ||
|
|
e05ce6aff0 | ||
|
|
e4598ba84a | ||
|
|
79513077f3 | ||
|
|
72acadca79 | ||
|
|
398e358826 | ||
|
|
7fc9b0fe97 | ||
|
|
71cc967f42 | ||
|
|
3ddad1e377 | ||
|
|
92805d3088 | ||
|
|
60acd05a03 | ||
|
|
4a18e241d9 | ||
|
|
1b88a1e3c7 | ||
|
|
b3feefafdf | ||
|
|
a7a16db908 | ||
|
|
9f92ba34a6 | ||
|
|
25cf039953 | ||
|
|
6bf145d7cf | ||
|
|
69240e18b4 | ||
|
|
5b9f431512 |
10
README.md
10
README.md
@@ -52,6 +52,7 @@ TinyObjLoader is successfully used in ...
|
||||
### New version(v1.0.x)
|
||||
|
||||
* 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!
|
||||
|
||||
### Old version(v0.9.x)
|
||||
@@ -73,6 +74,11 @@ TinyObjLoader is successfully used in ...
|
||||
* 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
|
||||
* 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
|
||||
|
||||
@@ -83,6 +89,7 @@ TinyObjLoader is successfully used in ...
|
||||
* Material
|
||||
* Unknown material attributes are returned as key-value(value is string) map.
|
||||
* 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.
|
||||
|
||||
|
||||
@@ -90,6 +97,9 @@ TinyObjLoader is successfully used in ...
|
||||
|
||||
* [ ] Fix obj_sticker example.
|
||||
* [ ] More unit test codes.
|
||||
* [ ] Texture options
|
||||
* [ ] Normal vector generation
|
||||
* [ ] Support smoothing groups
|
||||
|
||||
## License
|
||||
|
||||
|
||||
6755
examples/viewer/stb_image.h
Normal file
6755
examples/viewer/stb_image.h
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,7 @@
|
||||
#include <cstdlib>
|
||||
#include <iostream>
|
||||
#include <limits>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
@@ -26,12 +27,24 @@
|
||||
|
||||
#include "trackball.h"
|
||||
|
||||
#define STB_IMAGE_IMPLEMENTATION
|
||||
#include "stb_image.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
#include <mmsystem.h>
|
||||
#include <windows.h>
|
||||
|
||||
#ifdef max
|
||||
#undef max
|
||||
#endif
|
||||
|
||||
#ifdef min
|
||||
#undef min
|
||||
#endif
|
||||
|
||||
#include <mmsystem.h>
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
@@ -112,6 +125,7 @@ class timerutil {
|
||||
typedef struct {
|
||||
GLuint vb; // vertex buffer
|
||||
int numTriangles;
|
||||
size_t material_id;
|
||||
} DrawObject;
|
||||
|
||||
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],
|
||||
std::vector<DrawObject>* drawObjects,
|
||||
std::vector<tinyobj::material_t>& materials,
|
||||
std::map<std::string, GLuint>& textures,
|
||||
const char* filename) {
|
||||
tinyobj::attrib_t attrib;
|
||||
std::vector<tinyobj::shape_t> shapes;
|
||||
std::vector<tinyobj::material_t> materials;
|
||||
|
||||
timerutil tm;
|
||||
|
||||
@@ -194,17 +209,82 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
|
||||
printf("# of materials = %d\n", (int)materials.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();
|
||||
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
|
||||
|
||||
{
|
||||
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;
|
||||
std::vector<float> vb; // pos(3float), normal(3float), color(3float)
|
||||
for (size_t f = 0; f < shapes[s].mesh.indices.size() / 3; f++) {
|
||||
tinyobj::index_t idx0 = shapes[s].mesh.indices[3 * f + 0];
|
||||
tinyobj::index_t idx1 = shapes[s].mesh.indices[3 * f + 1];
|
||||
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];
|
||||
for (int k = 0; k < 3; k++) {
|
||||
@@ -227,7 +307,6 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3],
|
||||
}
|
||||
|
||||
float n[3][3];
|
||||
|
||||
if (attrib.normals.size() > 0) {
|
||||
int f0 = idx0.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][1]);
|
||||
vb.push_back(n[k][2]);
|
||||
// Use normal as color.
|
||||
float c[3] = {n[k][0], n[k][1], n[k][2]};
|
||||
// Combine normal and diffuse to get color.
|
||||
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];
|
||||
if (len2 > 0.0f) {
|
||||
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[1] * 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.numTriangles = 0;
|
||||
o.material_id = current_material_id;
|
||||
if (vb.size() > 0) {
|
||||
glGenBuffers(1, &o.vb);
|
||||
glBindBuffer(GL_ARRAY_BUFFER, o.vb);
|
||||
glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0),
|
||||
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),
|
||||
o.numTriangles);
|
||||
}
|
||||
@@ -395,13 +484,13 @@ void motionFunc(GLFWwindow* window, double mouse_x, double 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_BACK, GL_FILL);
|
||||
|
||||
glEnable(GL_POLYGON_OFFSET_FILL);
|
||||
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++) {
|
||||
DrawObject o = drawObjects[i];
|
||||
if (o.vb < 1) {
|
||||
@@ -412,12 +501,20 @@ void Draw(const std::vector<DrawObject>& drawObjects) {
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
glEnableClientState(GL_COLOR_ARRAY);
|
||||
glVertexPointer(3, GL_FLOAT, 36, (const void*)0);
|
||||
glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float) * 3));
|
||||
glColorPointer(3, GL_FLOAT, 36, (const void*)(sizeof(float) * 6));
|
||||
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
|
||||
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);
|
||||
CheckErrors("drawarrays");
|
||||
glBindTexture(GL_TEXTURE_2D, 0);
|
||||
}
|
||||
|
||||
// draw wireframe
|
||||
@@ -436,8 +533,11 @@ void Draw(const std::vector<DrawObject>& drawObjects) {
|
||||
glEnableClientState(GL_VERTEX_ARRAY);
|
||||
glEnableClientState(GL_NORMAL_ARRAY);
|
||||
glDisableClientState(GL_COLOR_ARRAY);
|
||||
glVertexPointer(3, GL_FLOAT, 36, (const void*)0);
|
||||
glNormalPointer(GL_FLOAT, 36, (const void*)(sizeof(float) * 3));
|
||||
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
|
||||
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);
|
||||
CheckErrors("drawarrays");
|
||||
@@ -498,7 +598,9 @@ int main(int argc, char** argv) {
|
||||
reshapeFunc(window, width, height);
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
@@ -516,6 +618,7 @@ int main(int argc, char** argv) {
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_TEXTURE_2D);
|
||||
|
||||
// camera & rotate
|
||||
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]),
|
||||
-0.5 * (bmax[2] + bmin[2]));
|
||||
|
||||
Draw(gDrawObjects);
|
||||
Draw(gDrawObjects, materials, textures);
|
||||
|
||||
glfwSwapBuffers(window);
|
||||
}
|
||||
|
||||
5
experimental/README.md
Normal file
5
experimental/README.md
Normal 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.
|
||||
@@ -3,6 +3,11 @@ newoption {
|
||||
description = "Build with zlib."
|
||||
}
|
||||
|
||||
newoption {
|
||||
trigger = "with-zstd",
|
||||
description = "Build with ZStandard compression."
|
||||
}
|
||||
|
||||
solution "objview"
|
||||
-- location ( "build" )
|
||||
configurations { "Release", "Debug" }
|
||||
@@ -23,6 +28,15 @@ solution "objview"
|
||||
links { 'z' }
|
||||
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)
|
||||
--buildoptions { "-fsanitize=address" }
|
||||
--linkoptions { "-fsanitize=address" }
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
#include <zlib.h>
|
||||
#endif
|
||||
|
||||
#if defined(ENABLE_ZSTD)
|
||||
#include <zstd.h>
|
||||
#endif
|
||||
|
||||
#include <GL/glew.h>
|
||||
|
||||
#ifdef __APPLE__
|
||||
@@ -182,6 +186,71 @@ bool gz_load(std::vector<char>* buf, const char* filename)
|
||||
#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)
|
||||
{
|
||||
|
||||
@@ -204,6 +273,20 @@ const char* get_file_data(size_t *len, const char* filename)
|
||||
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 {
|
||||
|
||||
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::material_t> materials;
|
||||
|
||||
auto load_t_begin = std::chrono::high_resolution_clock::now();
|
||||
size_t data_len = 0;
|
||||
const char* data = get_file_data(&data_len, filename);
|
||||
if (data == nullptr) {
|
||||
printf("failed to load file\n");
|
||||
exit(-1);
|
||||
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;
|
||||
option.req_num_threads = num_threads;
|
||||
option.verbose = verbose;
|
||||
@@ -534,6 +626,7 @@ int main(int argc, char **argv)
|
||||
size_t data_len = 0;
|
||||
const char* data = get_file_data(&data_len, argv[1]);
|
||||
if (data == nullptr) {
|
||||
printf("failed to load file\n");
|
||||
exit(-1);
|
||||
return false;
|
||||
}
|
||||
|
||||
30
models/usemtl-issue-104.obj
Normal file
30
models/usemtl-issue-104.obj
Normal 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
|
||||
@@ -184,6 +184,48 @@ TestLoadObj(
|
||||
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
|
||||
TestStreamLoadObj()
|
||||
@@ -351,6 +393,16 @@ TEST_CASE("stream_load", "[Stream]") {
|
||||
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]") {
|
||||
tinyobj::attrib_t attrib;
|
||||
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.3 == Approx(materials[0].transmittance[2]));
|
||||
}
|
||||
|
||||
TEST_CASE("transmittance_filter_Kt", "[Issue95-Kt]") {
|
||||
tinyobj::attrib_t attrib;
|
||||
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]));
|
||||
}
|
||||
|
||||
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
|
||||
int
|
||||
main(
|
||||
|
||||
@@ -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.
|
||||
//
|
||||
|
||||
@@ -91,7 +92,7 @@ typedef struct {
|
||||
std::vector<std::string> stringValues;
|
||||
} 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.
|
||||
typedef struct {
|
||||
int vertex_index;
|
||||
@@ -179,6 +180,19 @@ class MaterialFileReader : public MaterialReader {
|
||||
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.
|
||||
/// 'attrib', 'shapes' and 'materials' 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`
|
||||
bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
|
||||
std::vector<material_t> *materials, std::string *err,
|
||||
std::istream *inStream, MaterialReader *readMatFn,
|
||||
std::istream *inStream, MaterialReader *readMatFn = NULL,
|
||||
bool triangulate = true);
|
||||
|
||||
/// Loads materials into std::map
|
||||
@@ -1010,6 +1024,22 @@ bool MaterialFileReader::operator()(const std::string &matId,
|
||||
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,
|
||||
std::vector<material_t> *materials, std::string *err,
|
||||
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,
|
||||
std::vector<material_t> *materials, std::string *err,
|
||||
std::istream *inStream, MaterialReader *readMatFn,
|
||||
std::istream *inStream,
|
||||
MaterialReader *readMatFn /*= NULL*/,
|
||||
bool triangulate) {
|
||||
std::stringstream errss;
|
||||
|
||||
@@ -1161,7 +1192,9 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
|
||||
}
|
||||
|
||||
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,
|
||||
triangulate);
|
||||
faceGroup.clear();
|
||||
@@ -1173,23 +1206,25 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
|
||||
|
||||
// load mtl
|
||||
if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
|
||||
char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
|
||||
token += 7;
|
||||
if (readMatFn) {
|
||||
char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
|
||||
token += 7;
|
||||
#ifdef _MSC_VER
|
||||
sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
|
||||
sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
|
||||
#else
|
||||
sscanf(token, "%s", namebuf);
|
||||
sscanf(token, "%s", namebuf);
|
||||
#endif
|
||||
|
||||
std::string err_mtl;
|
||||
bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl);
|
||||
if (err) {
|
||||
(*err) += err_mtl;
|
||||
}
|
||||
std::string err_mtl;
|
||||
bool ok = (*readMatFn)(namebuf, materials, &material_map, &err_mtl);
|
||||
if (err) {
|
||||
(*err) += err_mtl;
|
||||
}
|
||||
|
||||
if (!ok) {
|
||||
faceGroup.clear(); // for safety
|
||||
return false;
|
||||
if (!ok) {
|
||||
faceGroup.clear(); // for safety
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
continue;
|
||||
@@ -1307,7 +1342,11 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
|
||||
|
||||
bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name,
|
||||
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);
|
||||
}
|
||||
faceGroup.clear(); // for safety
|
||||
|
||||
Reference in New Issue
Block a user