89 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
Syoyo Fujita
ad79762212 Update README. 2016-08-20 18:03:18 +09:00
Syoyo Fujita
64149943cc Add link to examples. 2016-08-20 18:01:27 +09:00
Syoyo Fujita
714194d353 Merge branch 'master' of github.com:syoyo/tinyobjloader 2016-08-20 17:58:57 +09:00
Syoyo Fujita
9d81c24934 Update AppVeyor script. 2016-08-20 17:54:24 +09:00
Syoyo Fujita
25c194bf71 Update README. 2016-08-20 17:53:07 +09:00
Syoyo Fujita
5a4c5ff668 Merge branch 'develop' 2016-08-20 17:49:59 +09:00
Syoyo Fujita
f06e814d7c Update README. 2016-08-20 16:23:41 +09:00
Syoyo Fujita
5eef2b0914 Fix python binding. 2016-08-20 16:23:20 +09:00
Syoyo Fujita
49988672f4 Add link to tinyobjloader-c. 2016-08-19 20:22:26 +09:00
Syoyo Fujita
d192402800 Support Tf in MTL. 2016-08-19 20:19:28 +09:00
Syoyo Fujita
646f1312f1 Parse 'Tf' field in MTL. 2016-08-18 16:13:27 +09:00
Syoyo Fujita
db62a6c1cc Merge pull request #95 from graysonlang/patch-1
Fix MTL token for transmission filter.
2016-08-18 11:49:51 +09:00
Grayson Lang
8e53519a27 Ooops, meant "Tf" not "TF". 2016-08-17 13:34:18 -07:00
Grayson Lang
d3d6932efd Fix MTL "transmission filter" token
The "transmission filter" is currently set to Kt, which is undocumented. Adding support for the specified token of "Tf".
2016-08-17 13:30:25 -07:00
Syoyo Fujita
dea325cdcb Fix face num calculation. 2016-08-13 19:27:13 +09:00
Syoyo Fujita
2b50b31657 Return parsed material information in parseObj API..
Parse PBR extension in MTL
2016-08-13 18:38:45 +09:00
Syoyo Fujita
110b49a0c8 Rename vertex_index struct. 2016-08-13 15:27:35 +09:00
Syoyo Fujita
319746d3db Fix parsing benchmark flag. 2016-08-13 15:09:58 +09:00
Syoyo Fujita
2cb73fa85d Fix the ordefing of a constructor call of vertex_index. 2016-08-13 02:52:36 +09:00
Syoyo Fujita
5a832b470a Update README. 2016-08-12 23:13:31 +09:00
Syoyo Fujita
42f04024d4 Remove old version log. 2016-08-12 23:13:05 +09:00
Syoyo Fujita
07852e206d Apply clang-format. 2016-08-12 23:12:46 +09:00
Syoyo Fujita
156b47760d Merge branch 'develop' of github.com:syoyo/tinyobjloader into develop 2016-08-12 20:40:16 +09:00
Syoyo Fujita
673501749f Apply clang-format. 2016-08-12 20:39:42 +09:00
Syoyo Fujita
b56fc1a0cc Fix appveyor script. 2016-08-12 19:01:51 +09:00
Syoyo Fujita
2ed3222bbe Compute geometric normal where vertex normal is not available. 2016-08-12 18:58:34 +09:00
Syoyo Fujita
c7da23795d Add static keyword to safeGetline(). 2016-08-05 19:16:46 +09:00
Syoyo Fujita
8ca2bc0de7 Fix appveyor script. 2016-08-02 17:14:46 +09:00
Syoyo Fujita
0a85945767 Skip trailing whitespace in mtl. Fixes #92. 2016-08-02 17:04:00 +09:00
Syoyo Fujita
51d13700d8 Skip trailing whitespace in mtl. Fixes #92. 2016-08-02 16:55:50 +09:00
Syoyo Fujita
1983e889dc Update callback API example.
Update API document.
2016-07-28 16:21:48 +09:00
Syoyo Fujita
dfe9d7bcae Merge pull request #91 from adishavit/develop
Changes the value indicating non-existent index to 0.
2016-07-28 16:16:38 +09:00
Adi Shavit @ MacBookPro
4dee4cc673 Changes the value indicating non-existent index to 0. OBJ indices are 1 (or -1) based, so 0 is an invalid value. 2016-07-28 10:12:16 +03:00
Syoyo Fujita
a7ea651bef Suppress clang warnings. 2016-07-28 00:30:52 +09:00
Syoyo Fujita
4c2afb8814 Merge pull request #90 from adishavit/develop
Changes the LoadObjWithCallback() API
2016-07-27 17:45:49 +09:00
Adi Shavit @ MacBookPro
951833812a Changes the LoadObjWithCallback() API to accept std::istream& instead of pointed since this is a required argument.
- Also changing the argument order to allow for defaults for optional arguments.
2016-07-27 11:38:25 +03:00
Syoyo Fujita
e456cc949d Merge pull request #89 from adishavit/develop
Crash bug fix + optimizations in LoadObjWithCallback()
2016-07-27 16:30:36 +09:00
Syoyo Fujita
75e64cd47a Support parsing w element of vertex coordinate, and also third element of texture coordinates. Fixes #88. 2016-07-27 16:16:51 +09:00
Adi Shavit @ MacBookPro
3736a5791f Fix a crash bug in LoadObjWithCallback() when passing a nullptr as MaterialReader *readMatFn.
- This preserves the existing API. If `nullptr` is passed then the material file will be ignored. This is useful when the user is not interested in the materials.
- Note that if `nullptr` is not a valid option for this function, then the API needs to be changed to take `MaterialReader&`.
2016-07-27 10:04:22 +03:00
Adi Shavit @ MacBookPro
0b0bf60137 Move heap-based objects like vectors and strings outside the parse loop in LoadObjWithCallback().
This avoids reallocation on every use making the code faster.
2016-07-27 10:00:13 +03:00
Syoyo Fujita
d496d8eab6 Remove some invalid comments. Fix calling index_cb per f line. Fixes #87. 2016-07-27 00:02:53 +09:00
Syoyo Fujita
22883def8d Support PBR extension for MTL which is proposed in http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr 2016-07-25 22:46:30 +09:00
Syoyo Fujita
5826f1e149 Merge branch 'develop' of github.com:syoyo/tinyobjloader into develop 2016-07-25 19:03:42 +09:00
Syoyo Fujita
ef6560298e Add Vulkan Tutorial to the list. 2016-07-25 19:02:51 +09:00
Syoyo Fujita
e3a56816d6 Use std::getline() to read arbitrary size of line data. 2016-07-24 15:53:05 +09:00
Syoyo Fujita
5ef400882a Fix pow computation. 2016-07-15 13:25:44 +09:00
Syoyo Fujita
333bb55d84 Revert memcpy'ing input string into local buffer. 2016-07-15 02:01:50 +09:00
Syoyo Fujita
7d6318c3ad Remove redundant & buggy memcpy.
Add verbose option.
2016-07-15 01:38:39 +09:00
Syoyo Fujita
d5c722125a Show parsing time. 2016-07-09 00:52:27 +09:00
Syoyo Fujita
16ed0ac129 Update README. 2016-07-06 01:36:16 +09:00
Syoyo Fujita
b69d2a2c55 Add more links. 2016-05-29 16:37:30 +09:00
Syoyo Fujita
e210379335 Add Probulator 2016-05-29 14:08:27 +09:00
Syoyo Fujita
659976b8f3 Merge branch 'master' of github.com:syoyo/tinyobjloader 2016-05-29 13:32:24 +09:00
Syoyo Fujita
96ba498d70 Add voxelizer. 2016-05-29 13:09:00 +09:00
Syoyo Fujita
7ecb0b2f37 Use sefe getline for files with different line breaks. Fixes #81. 2016-05-15 16:20:35 +09:00
Syoyo Fujita
a14bbdb065 Merge pull request #80 from Vazquinhos/master
Fixing errors normal generation no triangulation. Remove warnings. Coding Style.
2016-05-13 21:18:48 +09:00
Vazquinhos
bfedfbb1fb Merge remote-tracking branch 'refs/remotes/syoyo/master'
# Conflicts:
#	tiny_obj_loader.h
2016-05-13 14:03:03 +02:00
Vazquinhos
41f46c7fd7 Error fixed with no triangulation. Removed warnings and modified coding brace style 2016-05-13 13:54:27 +02:00
Vazquinhos
a62dd278e2 Merge remote-tracking branch 'refs/remotes/syoyo/master' 2016-05-13 13:19:09 +02:00
Syoyo Fujita
a20e4ede85 Update document and version. 2016-05-13 20:09:49 +09:00
Syoyo Fujita
1ab0d147cb Merge pull request #79 from Vazquinhos/master
Flat normals calculation of obj without normals
2016-05-13 19:46:47 +09:00
Vazquinhos
9aee576b99 Added dependencies to cmath in order to use sqrt 2016-05-13 12:37:24 +02:00
Vazquinhos
0dcc72239d Files deleted of compilation log 2016-05-13 12:29:42 +02:00
Vazquinhos
33d5e9aa07 Test.cc modified with the new flags 2016-05-13 12:28:10 +02:00
Vazquinhos
e528741a8b Flat normals calculation of objects that their normals are empty 2016-05-13 12:25:48 +02:00
24 changed files with 8462 additions and 837 deletions

View File

@@ -1,5 +1,4 @@
tinyobjloader
=============
# tinyobjloader
[![Join the chat at https://gitter.im/syoyo/tinyobjloader](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/syoyo/tinyobjloader?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
@@ -17,29 +16,24 @@ Tiny but powerful single file wavefront obj loader written in C++. No dependency
`tinyobjloader` is good for embedding .obj loader to your (global illumination) renderer ;-)
If you are looking for C89 version, please see https://github.com/syoyo/tinyobjloader-c .
What's new
----------
* XX YY, ZZZZ : New data strcutre and API!
* Jan 29, 2016 : Support n-polygon(no triangulation) and OpenSubdiv crease tag! Thanks dboogert!
* Nov 26, 2015 : Now single-header only!.
* Nov 08, 2015 : Improved API.
* Jun 23, 2015 : Various fixes and added more projects using tinyobjloader. Thanks many contributors!
* Mar 03, 2015 : Replace atof() with hand-written parser for robust reading of numeric value. Thanks skurmedel!
* Feb 06, 2015 : Fix parsing multi-material object
* Sep 14, 2014 : Add support for multi-material per object/group. Thanks Mykhailo!
* Mar 17, 2014 : Fixed trim newline bugs. Thanks ardneran!
* Apr 29, 2014 : Add API to read .obj from std::istream. Good for reading compressed .obj or connecting to procedural primitive generator. Thanks burnse!
* Apr 21, 2014 : Define default material if no material definition exists in .obj. Thanks YarmUI!
* Apr 10, 2014 : Add support for parsing 'illum' and 'd'/'Tr' statements. Thanks mmp!
* Jan 27, 2014 : Added CMake project. Thanks bradc6!
* Nov 26, 2013 : Performance optimization by NeuralSandwich. 9% improvement in his project, thanks!
* Sep 12, 2013 : Added multiple .obj sticher example.
Example
Notice!
-------
We have released new version v1.0.0 on 20 Aug, 2016.
Old version is available `v0.9.x` branch https://github.com/syoyo/tinyobjloader/tree/v0.9.x
## What's new
* 20 Aug, 2016 : Bump version v1.0.0. New data strcutre and API!
### Old version
Previous old version is avaiable in `v0.9.x` branch.
## Example
![Rungholt](images/rungholt.jpg)
tinyobjloader can successfully load 6M triangles Rungholt scene.
@@ -48,12 +42,21 @@ http://graphics.cs.williams.edu/data/meshes.xml
![](images/sanmugel.png)
* [examples/viewer/](examples/viewer) OpenGL .obj viewer
* [examples/callback_api/](examples/callback_api/) Callback API example
* [examples/voxelize/](examples/voxelize/) Voxelizer example
Use case
--------
## Use case
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)
* bullet3 https://github.com/erwincoumans/bullet3
* pbrt-v2 https://github.com/mmp/pbrt-v2
* OpenGL game engine development http://swarminglogic.com/jotting/2013_10_gamedev01
@@ -65,10 +68,19 @@ TinyObjLoader is successfully used in ...
* pbrt-v3 https://github.com/mmp/pbrt-v3
* cocos2d-x https://github.com/cocos2d/cocos2d-x/
* Android Vulkan demo https://github.com/SaschaWillems/Vulkan
* Your project here!
* voxelizer https://github.com/karimnaaji/voxelizer
* Probulator https://github.com/kayru/Probulator
* OptiX Prime baking https://github.com/nvpro-samples/optix_prime_baking
* 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
--------
## Features
* Group(parse multiple group name)
* Vertex
@@ -77,23 +89,23 @@ Features
* 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.
TODO
----
## TODO
* [ ] Fix Python binding.
* [ ] Fix obj_sticker example.
* [ ] More unit test codes.
* [ ] Texture options
* [ ] Normal vector generation
* [ ] Support smoothing groups
License
-------
## License
Licensed under MIT license.
Usage
-----
## Usage
`attrib_t` contains single and linear array of vertex data(position, normal and texcoord).
Each `shape_t` does not contain vertex data but contains array index to `attrib_t`.
@@ -148,8 +160,7 @@ for (size_t s = 0; s < shapes.size(); s++) {
```
Optimized loader
----------------
## Optimized loader
Optimized multi-threaded .obj loader is available at `experimental/` directory.
If you want absolute performance to load .obj data, this optimized loader will fit your purpose.
@@ -163,7 +174,6 @@ Here is some benchmark result. Time are measured on MacBook 12(Early 2016, Core
* optimised: 1500 msecs(10x faster than old version, 4.5x faster than basedline)
Tests
-----
## Tests
Unit tests are provided in `tests` directory. See `tests/README.md` for details.

View File

@@ -1,4 +1,4 @@
version: 0.9.{build}
version: 1.0.{build}
platform: x64
@@ -7,7 +7,6 @@ install:
# All external dependencies are installed in C:\projects\deps
#######################################################################################
- mkdir C:\projects\deps
- cd C:\projects\deps
#######################################################################################
# Install Ninja

View File

@@ -1,15 +1,21 @@
//
// An example of how to use callback API.
// This example is minimum and incomplete. Just showing the usage of callback
// API.
// You need to implement your own Mesh data struct constrution based on this
// example in practical.
//
#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <fstream>
#include <iostream>
#include <sstream>
#include <fstream>
typedef struct
{
typedef struct {
std::vector<float> vertices;
std::vector<float> normals;
std::vector<float> texcoords;
@@ -21,19 +27,18 @@ typedef struct
} MyMesh;
void vertex_cb(void *user_data, float x, float y, float z)
{
MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
printf("v[%ld] = %f, %f, %f\n", mesh->vertices.size() / 3, x, y, z);
void vertex_cb(void *user_data, float x, float y, float z, float w) {
MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
printf("v[%ld] = %f, %f, %f (w %f)\n", mesh->vertices.size() / 3, x, y, z, w);
mesh->vertices.push_back(x);
mesh->vertices.push_back(y);
mesh->vertices.push_back(z);
// Discard w
}
void normal_cb(void *user_data, float x, float y, float z)
{
MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
void normal_cb(void *user_data, float x, float y, float z) {
MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
printf("vn[%ld] = %f, %f, %f\n", mesh->normals.size() / 3, x, y, z);
mesh->normals.push_back(x);
@@ -41,49 +46,56 @@ void normal_cb(void *user_data, float x, float y, float z)
mesh->normals.push_back(z);
}
void texcoord_cb(void *user_data, float x, float y)
{
MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
printf("vt[%ld] = %f, %f\n", mesh->texcoords.size() / 2, x, y);
void texcoord_cb(void *user_data, float x, float y, float z) {
MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
printf("vt[%ld] = %f, %f, %f\n", mesh->texcoords.size() / 3, x, y, z);
mesh->texcoords.push_back(x);
mesh->texcoords.push_back(y);
mesh->texcoords.push_back(z);
}
void index_cb(void *user_data, int v_idx, int vn_idx, int vt_idx)
{
void index_cb(void *user_data, tinyobj::index_t *indices, int num_indices) {
// NOTE: the value of each index is raw value.
// For example, the application must manually adjust the index with offset
// (e.g. v_indices.size()) when the value is negative(relative index).
// (e.g. v_indices.size()) when the value is negative(whic means relative
// index).
// Also, the first index starts with 1, not 0.
// See fixIndex() function in tiny_obj_loader.h for details.
// Also, -2147483648(0x80000000) is set for the index value which does not exist in .obj
MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
printf("idx[%ld] = %d, %d, %d\n", mesh->v_indices.size(), v_idx, vn_idx, vt_idx);
// Also, 0 is set for the index value which
// does not exist in .obj
MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
if (v_idx != 0x80000000) {
mesh->v_indices.push_back(v_idx);
for (int i = 0; i < num_indices; i++) {
tinyobj::index_t idx = indices[i];
printf("idx[%ld] = %d, %d, %d\n", mesh->v_indices.size(), idx.vertex_index,
idx.normal_index, idx.texcoord_index);
if (idx.vertex_index != 0) {
mesh->v_indices.push_back(idx.vertex_index);
}
if (vn_idx != 0x80000000) {
mesh->vn_indices.push_back(vn_idx);
if (idx.normal_index != 0) {
mesh->vn_indices.push_back(idx.normal_index);
}
if (idx.texcoord_index != 0) {
mesh->vt_indices.push_back(idx.texcoord_index);
}
if (vt_idx != 0x80000000) {
mesh->vt_indices.push_back(vt_idx);
}
}
void usemtl_cb(void *user_data, const char* name, int material_idx)
{
MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
void usemtl_cb(void *user_data, const char *name, int material_idx) {
MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
if ((material_idx > -1) && (material_idx < mesh->materials.size())) {
printf("usemtl. material id = %d(name = %s)\n", material_idx, mesh->materials[material_idx].name.c_str());
printf("usemtl. material id = %d(name = %s)\n", material_idx,
mesh->materials[material_idx].name.c_str());
} else {
printf("usemtl. name = %s\n", name);
}
}
void mtllib_cb(void *user_data, const tinyobj::material_t *materials, int num_materials)
{
MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
void mtllib_cb(void *user_data, const tinyobj::material_t *materials,
int num_materials) {
MyMesh *mesh = reinterpret_cast<MyMesh *>(user_data);
printf("mtllib. # of materials = %d\n", num_materials);
for (int i = 0; i < num_materials; i++) {
@@ -91,9 +103,8 @@ void mtllib_cb(void *user_data, const tinyobj::material_t *materials, int num_ma
}
}
void group_cb(void *user_data, const char **names, int num_names)
{
//MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
void group_cb(void *user_data, const char **names, int num_names) {
// MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
printf("group : name = \n");
for (int i = 0; i < num_names; i++) {
@@ -101,16 +112,12 @@ void group_cb(void *user_data, const char **names, int num_names)
}
}
void object_cb(void *user_data, const char *name)
{
//MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
void object_cb(void *user_data, const char *name) {
// MyMesh *mesh = reinterpret_cast<MyMesh*>(user_data);
printf("object : name = %s\n", name);
}
int
main(int argc, char** argv)
{
int main(int argc, char **argv) {
tinyobj::callback_t cb;
cb.vertex_cb = vertex_cb;
cb.normal_cb = normal_cb;
@@ -123,7 +130,11 @@ main(int argc, char** argv)
MyMesh mesh;
std::string err;
std::ifstream ifs("../../models/cornell_box.obj");
std::string filename = "../../models/cornell_box.obj";
if (argc > 1) {
filename = std::string(argv[1]);
}
std::ifstream ifs(filename.c_str());
if (ifs.fail()) {
std::cerr << "file not found." << std::endl;
@@ -132,7 +143,7 @@ main(int argc, char** argv)
tinyobj::MaterialFileReader mtlReader("../../models/");
bool ret = tinyobj::LoadObjWithCallback(&mesh, cb, &err, &ifs, &mtlReader);
bool ret = tinyobj::LoadObjWithCallback(ifs, cb, &mesh, &mtlReader, &err);
if (!err.empty()) {
std::cerr << err << std::endl;

6755
examples/viewer/stb_image.h Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,16 @@
//
// Simple .obj viewer(vertex only)
//
#include <vector>
#include <string>
#include <algorithm>
#include <cassert>
#include <cmath>
#include <cstdio>
#include <cstdlib>
#include <iostream>
#include <limits>
#include <cmath>
#include <cassert>
#include <algorithm>
#include <map>
#include <string>
#include <vector>
#include <GL/glew.h>
@@ -26,11 +27,23 @@
#include "trackball.h"
#define STB_IMAGE_IMPLEMENTATION
#include "stb_image.h"
#ifdef _WIN32
#ifdef __cplusplus
extern "C" {
#endif
#include <windows.h>
#ifdef max
#undef max
#endif
#ifdef min
#undef min
#endif
#include <mmsystem.h>
#ifdef __cplusplus
}
@@ -45,7 +58,7 @@ extern "C" {
#endif
class timerutil {
public:
public:
#ifdef _WIN32
typedef DWORD time_t;
@@ -96,7 +109,7 @@ public:
#endif
#endif
private:
private:
#ifdef _WIN32
DWORD t_[2];
#else
@@ -112,6 +125,7 @@ private:
typedef struct {
GLuint vb; // vertex buffer
int numTriangles;
size_t material_id;
} DrawObject;
std::vector<DrawObject> gDrawObjects;
@@ -161,18 +175,21 @@ 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, const char* filename)
{
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;
tm.start();
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, NULL);
bool ret =
tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, NULL);
if (!err.empty()) {
std::cerr << err << std::endl;
}
@@ -184,7 +201,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector<DrawObject>& dr
return false;
}
printf("Parsing time: %d [ms]\n", tm.msec());
printf("Parsing time: %d [ms]\n", (int)tm.msec());
printf("# of vertices = %d\n", (int)(attrib.vertices.size()) / 3);
printf("# of normals = %d\n", (int)(attrib.normals.size()) / 3);
@@ -192,18 +209,82 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector<DrawObject>& dr
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++) {
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];
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++) {
@@ -214,9 +295,9 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector<DrawObject>& dr
assert(f1 >= 0);
assert(f2 >= 0);
v[0][k] = attrib.vertices[3*f0+k];
v[1][k] = attrib.vertices[3*f1+k];
v[2][k] = attrib.vertices[3*f2+k];
v[0][k] = attrib.vertices[3 * f0 + k];
v[1][k] = attrib.vertices[3 * f1 + k];
v[2][k] = attrib.vertices[3 * f2 + k];
bmin[k] = std::min(v[0][k], bmin[k]);
bmin[k] = std::min(v[1][k], bmin[k]);
bmin[k] = std::min(v[2][k], bmin[k]);
@@ -226,7 +307,6 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector<DrawObject>& dr
}
float n[3][3];
if (attrib.normals.size() > 0) {
int f0 = idx0.normal_index;
int f1 = idx1.normal_index;
@@ -235,15 +315,19 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector<DrawObject>& dr
assert(f1 >= 0);
assert(f2 >= 0);
for (int k = 0; k < 3; k++) {
n[0][k] = attrib.normals[3*f0+k];
n[1][k] = attrib.normals[3*f1+k];
n[2][k] = attrib.normals[3*f2+k];
n[0][k] = attrib.normals[3 * f0 + k];
n[1][k] = attrib.normals[3 * f1 + k];
n[2][k] = attrib.normals[3 * f2 + k];
}
} else {
// compute geometric normal
CalcNormal(n[0], v[0], v[1], v[2]);
n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2];
n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2];
n[1][0] = n[0][0];
n[1][1] = n[0][1];
n[1][2] = n[0][2];
n[2][0] = n[0][0];
n[2][1] = n[0][1];
n[2][2] = n[0][2];
}
for (int k = 0; k < 3; k++) {
@@ -253,8 +337,14 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector<DrawObject>& dr
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);
@@ -266,21 +356,26 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector<DrawObject>& dr
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;
printf("shape[%d] # of triangles = %d\n", static_cast<int>(s), o.numTriangles);
glBufferData(GL_ARRAY_BUFFER, vb.size() * sizeof(float), &vb.at(0),
GL_STATIC_DRAW);
o.numTriangles = vb.size() / (3 + 3 + 3 + 2) * 3;
printf("shape[%d] # of triangles = %d\n", static_cast<int>(s),
o.numTriangles);
}
gDrawObjects.push_back(o);
drawObjects->push_back(o);
}
}
@@ -290,8 +385,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], std::vector<DrawObject>& dr
return true;
}
void reshapeFunc(GLFWwindow* window, int w, int h)
{
void reshapeFunc(GLFWwindow* window, int w, int h) {
int fb_w, fb_h;
// Get actual framebuffer size.
glfwGetFramebufferSize(window, &fb_w, &fb_h);
@@ -307,56 +401,69 @@ void reshapeFunc(GLFWwindow* window, int w, int h)
height = h;
}
void keyboardFunc(GLFWwindow *window, int key, int scancode, int action, int mods) {
if(action == GLFW_PRESS || action == GLFW_REPEAT){
void keyboardFunc(GLFWwindow* window, int key, int scancode, int action,
int mods) {
(void)window;
(void)scancode;
(void)mods;
if (action == GLFW_PRESS || action == GLFW_REPEAT) {
// Move camera
float mv_x = 0, mv_y = 0, mv_z = 0;
if(key == GLFW_KEY_K) mv_x += 1;
else if(key == GLFW_KEY_J) mv_x += -1;
else if(key == GLFW_KEY_L) mv_y += 1;
else if(key == GLFW_KEY_H) mv_y += -1;
else if(key == GLFW_KEY_P) mv_z += 1;
else if(key == GLFW_KEY_N) mv_z += -1;
//camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05);
if (key == GLFW_KEY_K)
mv_x += 1;
else if (key == GLFW_KEY_J)
mv_x += -1;
else if (key == GLFW_KEY_L)
mv_y += 1;
else if (key == GLFW_KEY_H)
mv_y += -1;
else if (key == GLFW_KEY_P)
mv_z += 1;
else if (key == GLFW_KEY_N)
mv_z += -1;
// camera.move(mv_x * 0.05, mv_y * 0.05, mv_z * 0.05);
// Close window
if(key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE) glfwSetWindowShouldClose(window, GL_TRUE);
if (key == GLFW_KEY_Q || key == GLFW_KEY_ESCAPE)
glfwSetWindowShouldClose(window, GL_TRUE);
//init_frame = true;
// init_frame = true;
}
}
void clickFunc(GLFWwindow* window, int button, int action, int mods){
if(button == GLFW_MOUSE_BUTTON_LEFT){
if(action == GLFW_PRESS){
void clickFunc(GLFWwindow* window, int button, int action, int mods) {
(void)window;
(void)mods;
if (button == GLFW_MOUSE_BUTTON_LEFT) {
if (action == GLFW_PRESS) {
mouseLeftPressed = true;
trackball(prev_quat, 0.0, 0.0, 0.0, 0.0);
} else if(action == GLFW_RELEASE){
} else if (action == GLFW_RELEASE) {
mouseLeftPressed = false;
}
}
if(button == GLFW_MOUSE_BUTTON_RIGHT){
if(action == GLFW_PRESS){
if (button == GLFW_MOUSE_BUTTON_RIGHT) {
if (action == GLFW_PRESS) {
mouseRightPressed = true;
} else if(action == GLFW_RELEASE){
} else if (action == GLFW_RELEASE) {
mouseRightPressed = false;
}
}
if(button == GLFW_MOUSE_BUTTON_MIDDLE){
if(action == GLFW_PRESS){
if (button == GLFW_MOUSE_BUTTON_MIDDLE) {
if (action == GLFW_PRESS) {
mouseMiddlePressed = true;
} else if(action == GLFW_RELEASE){
} else if (action == GLFW_RELEASE) {
mouseMiddlePressed = false;
}
}
}
void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y){
void motionFunc(GLFWwindow* window, double mouse_x, double mouse_y) {
(void)window;
float rotScale = 1.0f;
float transScale = 2.0f;
if(mouseLeftPressed){
trackball(prev_quat,
rotScale * (2.0f * prevMouseX - width) / (float)width,
if (mouseLeftPressed) {
trackball(prev_quat, rotScale * (2.0f * prevMouseX - width) / (float)width,
rotScale * (height - 2.0f * prevMouseY) / (float)height,
rotScale * (2.0f * mouse_x - width) / (float)width,
rotScale * (height - 2.0f * mouse_y) / (float)height);
@@ -377,14 +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) {
@@ -395,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
@@ -419,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");
@@ -443,9 +560,7 @@ static void Init() {
up[2] = 0.0f;
}
int main(int argc, char **argv)
{
int main(int argc, char** argv) {
if (argc < 2) {
std::cout << "Needs input.obj\n" << std::endl;
return 0;
@@ -453,16 +568,13 @@ int main(int argc, char **argv)
Init();
if(!glfwInit()){
if (!glfwInit()) {
std::cerr << "Failed to initialize GLFW." << std::endl;
return -1;
}
window = glfwCreateWindow(width, height, "Obj viewer", NULL, NULL);
if(window == NULL){
if (window == NULL) {
std::cerr << "Failed to open GLFW window. " << std::endl;
glfwTerminate();
return 1;
@@ -486,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;
}
@@ -498,18 +612,20 @@ int main(int argc, char **argv)
maxExtent = 0.5f * (bmax[2] - bmin[2]);
}
while(glfwWindowShouldClose(window) == GL_FALSE) {
while (glfwWindowShouldClose(window) == GL_FALSE) {
glfwPollEvents();
glClearColor(0.1f, 0.2f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glEnable(GL_TEXTURE_2D);
// camera & rotate
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
GLfloat mat[4][4];
gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0], up[1], up[2]);
gluLookAt(eye[0], eye[1], eye[2], lookat[0], lookat[1], lookat[2], up[0],
up[1], up[2]);
build_rotmatrix(mat, curr_quat);
glMultMatrixf(&mat[0][0]);
@@ -517,9 +633,10 @@ int main(int argc, char **argv)
glScalef(1.0f / maxExtent, 1.0f / maxExtent, 1.0f / maxExtent);
// Centerize object.
glTranslatef(-0.5*(bmax[0] + bmin[0]), -0.5*(bmax[1] + bmin[1]), -0.5*(bmax[2] + bmin[2]));
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
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."
}
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" }

View File

@@ -292,6 +292,22 @@ typedef struct {
std::string bump_texname; // map_bump, bump
std::string displacement_texname; // disp
std::string alpha_texname; // map_d
// PBR extension
// http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
float roughness; // [0, 1] default 0
float metallic; // [0, 1] default 0
float sheen; // [0, 1] default 0
float clearcoat_thickness; // [0, 1] default 0
float clearcoat_roughness; // [0, 1] default 0
float anisotropy; // aniso. [0, 1] default 0
float anisotropy_rotation; // anisor. [0, 1] default 0
std::string roughness_texname; // map_Pr
std::string metallic_texname; // map_Pm
std::string sheen_texname; // map_Ps
std::string emissive_texname; // map_Ke
std::string normal_texname; // norm. For normal mapping.
std::map<std::string, std::string> unknown_parameter;
} material_t;
@@ -301,19 +317,20 @@ typedef struct {
unsigned int length;
} shape_t;
struct vertex_index {
int v_idx, vt_idx, vn_idx;
vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {}
explicit vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {}
vertex_index(int vidx, int vtidx, int vnidx)
: v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {}
struct index_t {
int vertex_index, texcoord_index, normal_index;
index_t() : vertex_index(-1), texcoord_index(-1), normal_index(-1) {}
explicit index_t(int idx)
: vertex_index(idx), texcoord_index(idx), normal_index(idx) {}
index_t(int vidx, int vtidx, int vnidx)
: vertex_index(vidx), texcoord_index(vtidx), normal_index(vnidx) {}
};
typedef struct {
std::vector<float, lt::allocator<float> > vertices;
std::vector<float, lt::allocator<float> > normals;
std::vector<float, lt::allocator<float> > texcoords;
std::vector<vertex_index, lt::allocator<vertex_index> > faces;
std::vector<index_t, lt::allocator<index_t> > indices;
std::vector<int, lt::allocator<int> > face_num_verts;
std::vector<int, lt::allocator<int> > material_ids;
} attrib_t;
@@ -386,12 +403,11 @@ static inline int fixIndex(int idx, int n) {
}
// Parse raw triples: i, i/j/k, i//k, i/j
static vertex_index parseRawTriple(const char **token) {
vertex_index vi(
static index_t parseRawTriple(const char **token) {
index_t vi(
static_cast<int>(0x80000000)); // 0x80000000 = -2147483648 = invalid
vi.v_idx = my_atoi((*token));
//(*token) += strcspn((*token), "/ \t\r");
vi.vertex_index = my_atoi((*token));
while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
(*token)[0] != '\t' && (*token)[0] != '\r') {
(*token)++;
@@ -404,8 +420,7 @@ static vertex_index parseRawTriple(const char **token) {
// i//k
if ((*token)[0] == '/') {
(*token)++;
vi.vn_idx = my_atoi((*token));
//(*token) += strcspn((*token), "/ \t\r");
vi.normal_index = my_atoi((*token));
while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
(*token)[0] != '\t' && (*token)[0] != '\r') {
(*token)++;
@@ -414,8 +429,7 @@ static vertex_index parseRawTriple(const char **token) {
}
// i/j/k or i/j
vi.vt_idx = my_atoi((*token));
//(*token) += strcspn((*token), "/ \t\r");
vi.texcoord_index = my_atoi((*token));
while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
(*token)[0] != '\t' && (*token)[0] != '\r') {
(*token)++;
@@ -426,8 +440,7 @@ static vertex_index parseRawTriple(const char **token) {
// i/j/k
(*token)++; // skip '/'
vi.vn_idx = my_atoi((*token));
//(*token) += strcspn((*token), "/ \t\r");
vi.normal_index = my_atoi((*token));
while ((*token)[0] != '\0' && (*token)[0] != '/' && (*token)[0] != ' ' &&
(*token)[0] != '\t' && (*token)[0] != '\r') {
(*token)++;
@@ -436,17 +449,17 @@ static vertex_index parseRawTriple(const char **token) {
}
static inline bool parseString(ShortString *s, const char **token) {
skip_space(token); //(*token) += strspn((*token), " \t");
size_t e = until_space((*token)); // strcspn((*token), " \t\r");
skip_space(token);
size_t e = until_space((*token));
(*s)->insert((*s)->end(), (*token), (*token) + e);
(*token) += e;
return true;
}
static inline int parseInt(const char **token) {
skip_space(token); //(*token) += strspn((*token), " \t");
skip_space(token);
int i = my_atoi((*token));
(*token) += until_space((*token)); // strcspn((*token), " \t\r");
(*token) += until_space((*token));
return i;
}
@@ -585,9 +598,9 @@ assemble :
{
// = pow(5.0, exponent);
double a = 5.0;
double a = 1.0;
for (int i = 0; i < exponent; i++) {
a = a * a;
a = a * 5.0;
}
*result =
//(sign == '+' ? 1 : -1) * ldexp(mantissa * pow(5.0, exponent), exponent);
@@ -601,13 +614,12 @@ fail:
}
static inline float parseFloat(const char **token) {
skip_space(token); //(*token) += strspn((*token), " \t");
skip_space(token);
#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER
float f = static_cast<float>(atof(*token));
(*token) += strcspn((*token), " \t\r");
#else
const char *end =
(*token) + until_space((*token)); // strcspn((*token), " \t\r");
const char *end = (*token) + until_space((*token));
double val = 0.0;
tryParseDouble((*token), end, &val);
float f = static_cast<float>(val);
@@ -665,6 +677,11 @@ static void LoadMtl(std::map<std::string, int> *material_map,
std::string linebuf(&buf[0]);
// Trim trailing whitespace.
if (linebuf.size() > 0) {
linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
}
// Trim newline '\r\n' or '\n'
if (linebuf.size() > 0) {
if (linebuf[linebuf.size() - 1] == '\n')
@@ -747,7 +764,8 @@ static void LoadMtl(std::map<std::string, int> *material_map,
}
// transmittance
if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) {
if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
(token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
token += 2;
float r, g, b;
parseFloat3(&r, &g, &b, &token);
@@ -795,6 +813,7 @@ static void LoadMtl(std::map<std::string, int> *material_map,
material.dissolve = parseFloat(&token);
continue;
}
if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) {
token += 2;
// Invert value of Tr(assume Tr is in range [0, 1])
@@ -802,6 +821,55 @@ static void LoadMtl(std::map<std::string, int> *material_map,
continue;
}
// PBR: roughness
if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
token += 2;
material.roughness = parseFloat(&token);
continue;
}
// PBR: metallic
if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
token += 2;
material.metallic = parseFloat(&token);
continue;
}
// PBR: sheen
if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
token += 2;
material.sheen = parseFloat(&token);
continue;
}
// PBR: clearcoat thickness
if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
token += 2;
material.clearcoat_thickness = parseFloat(&token);
continue;
}
// PBR: clearcoat roughness
if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
token += 4;
material.clearcoat_roughness = parseFloat(&token);
continue;
}
// PBR: anisotropy
if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
token += 6;
material.anisotropy = parseFloat(&token);
continue;
}
// PBR: anisotropy rotation
if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
token += 7;
material.anisotropy_rotation = parseFloat(&token);
continue;
}
// ambient texture
if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
token += 7;
@@ -858,6 +926,41 @@ static void LoadMtl(std::map<std::string, int> *material_map,
continue;
}
// PBR: roughness texture
if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
token += 7;
material.roughness_texname = token;
continue;
}
// PBR: metallic texture
if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
token += 7;
material.metallic_texname = token;
continue;
}
// PBR: sheen texture
if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
token += 7;
material.sheen_texname = token;
continue;
}
// PBR: emissive texture
if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
token += 7;
material.emissive_texname = token;
continue;
}
// PBR: normal map texture
if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
token += 5;
material.normal_texname = token;
continue;
}
// unknown parameter
const char *_space = strchr(token, ' ');
if (!_space) {
@@ -896,8 +999,8 @@ typedef struct {
float tx, ty;
// for f
std::vector<vertex_index, lt::allocator<vertex_index> > f;
// std::vector<vertex_index> f;
std::vector<index_t, lt::allocator<index_t> > f;
// std::vector<index_t> f;
std::vector<int, lt::allocator<int> > f_num_verts;
const char *group_name;
@@ -918,44 +1021,42 @@ struct CommandCount {
size_t num_vn;
size_t num_vt;
size_t num_f;
size_t num_faces;
size_t num_indices;
CommandCount() {
num_v = 0;
num_vn = 0;
num_vt = 0;
num_f = 0;
num_faces = 0;
num_indices = 0;
}
};
class
LoadOption
{
class LoadOption {
public:
LoadOption() : req_num_threads(-1), triangulate(true), verbose(false) {}
int req_num_threads;
bool triangulate;
bool verbose;
};
/// Parse wavefront .obj(.obj string data is expanded to linear char array
/// `buf')
/// -1 to req_num_threads use the number of HW threads in the running system.
bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
size_t len, const LoadOption& option);
bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes,
std::vector<material_t> *materials, const char *buf, size_t len,
const LoadOption &option);
#ifdef TINYOBJ_LOADER_OPT_IMPLEMENTATION
static bool parseLine(Command *command, const char *p, size_t p_len,
bool triangulate = true) {
// @todo { operate directly on pointer `p'. to do that, add range check for
// string operatoion against `p', since `p' is not null-terminated at p[p_len]
// }
char linebuf[4096];
assert(p_len < 4095);
// StackVector<char, 256> linebuf;
// linebuf->resize(p_len + 1);
memcpy(&linebuf, p, p_len);
memcpy(linebuf, p, p_len);
linebuf[p_len] = '\0';
const char *token = linebuf;
@@ -963,8 +1064,7 @@ static bool parseLine(Command *command, const char *p, size_t p_len,
command->type = COMMAND_EMPTY;
// Skip leading space.
// token += strspn(token, " \t");
skip_space(&token); //(*token) += strspn((*token), " \t");
skip_space(&token);
assert(token);
if (token[0] == '\0') { // empty line
@@ -978,7 +1078,7 @@ static bool parseLine(Command *command, const char *p, size_t p_len,
// vertex
if (token[0] == 'v' && IS_SPACE((token[1]))) {
token += 2;
float x, y, z;
float x = 0.0f, y = 0.0f, z = 0.0f;
parseFloat3(&x, &y, &z, &token);
command->vx = x;
command->vy = y;
@@ -990,7 +1090,7 @@ static bool parseLine(Command *command, const char *p, size_t p_len,
// normal
if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) {
token += 3;
float x, y, z;
float x = 0.0f, y = 0.0f, z = 0.0f;
parseFloat3(&x, &y, &z, &token);
command->nx = x;
command->ny = y;
@@ -1002,7 +1102,7 @@ static bool parseLine(Command *command, const char *p, size_t p_len,
// texcoord
if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
token += 3;
float x, y;
float x = 0.0f, y = 0.0f;
parseFloat2(&x, &y, &token);
command->tx = x;
command->ty = y;
@@ -1013,19 +1113,12 @@ static bool parseLine(Command *command, const char *p, size_t p_len,
// face
if (token[0] == 'f' && IS_SPACE((token[1]))) {
token += 2;
// token += strspn(token, " \t");
skip_space(&token);
StackVector<vertex_index, 8> f;
StackVector<index_t, 8> f;
while (!IS_NEW_LINE(token[0])) {
vertex_index vi = parseRawTriple(&token);
// printf("v = %d, %d, %d\n", vi.v_idx, vi.vn_idx, vi.vt_idx);
// if (callback.index_cb) {
// callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx);
//}
// size_t n = strspn(token, " \t\r");
// token += n;
index_t vi = parseRawTriple(&token);
skip_space_and_cr(&token);
f->push_back(vi);
@@ -1034,9 +1127,9 @@ static bool parseLine(Command *command, const char *p, size_t p_len,
command->type = COMMAND_F;
if (triangulate) {
vertex_index i0 = f[0];
vertex_index i1(-1);
vertex_index i2 = f[1];
index_t i0 = f[0];
index_t i1(-1);
index_t i2 = f[1];
for (size_t k = 2; k < f->size(); k++) {
i1 = i2;
@@ -1153,20 +1246,21 @@ static inline bool is_line_ending(const char *p, size_t i, size_t end_i) {
return false;
}
bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
size_t len, const LoadOption& option)
{
bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes,
std::vector<material_t> *materials, const char *buf, size_t len,
const LoadOption &option) {
attrib->vertices.clear();
attrib->normals.clear();
attrib->texcoords.clear();
attrib->faces.clear();
attrib->indices.clear();
attrib->face_num_verts.clear();
attrib->material_ids.clear();
shapes->clear();
if (len < 1) return false;
auto num_threads = (option.req_num_threads < 0) ? std::thread::hardware_concurrency()
auto num_threads = (option.req_num_threads < 0)
? std::thread::hardware_concurrency()
: option.req_num_threads;
num_threads =
std::max(1, std::min(static_cast<int>(num_threads), kMaxThreads));
@@ -1304,7 +1398,7 @@ bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
command_count[t].num_vt++;
} else if (command.type == COMMAND_F) {
command_count[t].num_f += command.f.size();
command_count[t].num_faces++;
command_count[t].num_indices += command.f_num_verts.size();
}
if (command.type == COMMAND_MTLLIB) {
@@ -1329,7 +1423,6 @@ bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
}
std::map<std::string, int> material_map;
std::vector<material_t> materials;
// Load material(if exits)
if (mtllib_i_index >= 0 && mtllib_t_index >= 0 &&
@@ -1344,7 +1437,7 @@ bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
std::ifstream ifs(material_filename);
if (ifs.good()) {
LoadMtl(&material_map, &materials, &ifs);
LoadMtl(&material_map, materials, &ifs);
// std::cout << "maetrials = " << materials.size() << std::endl;
@@ -1368,14 +1461,15 @@ bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
size_t num_vn = 0;
size_t num_vt = 0;
size_t num_f = 0;
size_t num_faces = 0;
size_t num_indices = 0;
for (size_t t = 0; t < num_threads; t++) {
num_v += command_count[t].num_v;
num_vn += command_count[t].num_vn;
num_vt += command_count[t].num_vt;
num_f += command_count[t].num_f;
num_faces += command_count[t].num_faces;
num_indices += command_count[t].num_indices;
}
// std::cout << "# v " << num_v << std::endl;
// std::cout << "# vn " << num_vn << std::endl;
// std::cout << "# vt " << num_vt << std::endl;
@@ -1389,9 +1483,9 @@ bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
attrib->vertices.resize(num_v * 3);
attrib->normals.resize(num_vn * 3);
attrib->texcoords.resize(num_vt * 2);
attrib->faces.resize(num_f);
attrib->face_num_verts.resize(num_faces);
attrib->material_ids.resize(num_faces);
attrib->indices.resize(num_f);
attrib->face_num_verts.resize(num_indices);
attrib->material_ids.resize(num_indices);
size_t v_offsets[kMaxThreads];
size_t n_offsets[kMaxThreads];
@@ -1410,7 +1504,7 @@ bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
n_offsets[t] = n_offsets[t - 1] + command_count[t - 1].num_vn;
t_offsets[t] = t_offsets[t - 1] + command_count[t - 1].num_vt;
f_offsets[t] = f_offsets[t - 1] + command_count[t - 1].num_f;
face_offsets[t] = face_offsets[t - 1] + command_count[t - 1].num_faces;
face_offsets[t] = face_offsets[t - 1] + command_count[t - 1].num_indices;
}
StackVector<std::thread, 16> workers;
@@ -1456,17 +1550,20 @@ bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
t_count++;
} else if (commands[t][i].type == COMMAND_F) {
for (size_t k = 0; k < commands[t][i].f.size(); k++) {
vertex_index &vi = commands[t][i].f[k];
int v_idx = fixIndex(vi.v_idx, v_count);
int vn_idx = fixIndex(vi.vn_idx, n_count);
int vt_idx = fixIndex(vi.vt_idx, t_count);
attrib->faces[f_count + k] = vertex_index(v_idx, vn_idx, vt_idx);
index_t &vi = commands[t][i].f[k];
int vertex_index = fixIndex(vi.vertex_index, v_count);
int texcoord_index = fixIndex(vi.texcoord_index, t_count);
int normal_index = fixIndex(vi.normal_index, n_count);
attrib->indices[f_count + k] =
index_t(vertex_index, texcoord_index, normal_index);
}
for (size_t k = 0; k < commands[t][i].f_num_verts.size(); k++) {
attrib->material_ids[face_count + k] = material_id;
attrib->face_num_verts[face_count + k] = commands[t][i].f_num_verts[k];
}
attrib->material_ids[face_count] = material_id;
attrib->face_num_verts[face_count] = commands[t][i].f.size();
f_count += commands[t][i].f.size();
face_count++;
face_count += commands[t][i].f_num_verts.size();
}
}
}));
@@ -1567,8 +1664,8 @@ bool parseObj(attrib_t *attrib, std::vector<shape_t> *shapes, const char *buf,
std::cout << "# of vertices = " << attrib->vertices.size() << std::endl;
std::cout << "# of normals = " << attrib->normals.size() << std::endl;
std::cout << "# of texcoords = " << attrib->texcoords.size() << std::endl;
std::cout << "# of face indices = " << attrib->faces.size() << std::endl;
std::cout << "# of faces = " << attrib->material_ids.size() << std::endl;
std::cout << "# of face indices = " << attrib->indices.size() << std::endl;
std::cout << "# of indices = " << attrib->material_ids.size() << std::endl;
std::cout << "# of shapes = " << shapes->size() << std::endl;
}

View File

@@ -86,7 +86,7 @@ static void vsub(const float *src1, const float *src2, float *dst) {
}
static void vcopy(const float *v1, float *v2) {
register int i;
int i;
for (i = 0; i < 3; i++)
v2[i] = v1[i];
}

View File

@@ -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);
@@ -214,26 +297,39 @@ const char* get_file_data(size_t *len, const char* filename)
}
bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads)
bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int num_threads, bool verbose)
{
#if 1
tinyobj_opt::attrib_t attrib;
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;
bool ret = parseObj(&attrib, &shapes, data, data_len, option);
option.verbose = verbose;
bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option);
bmin[0] = bmin[1] = bmin[2] = std::numeric_limits<float>::max();
bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits<float>::max();
//std::cout << "vertices.size() = " << attrib.vertices.size() << std::endl;
//std::cout << "normals.size() = " << attrib.normals.size() << std::endl;
{
DrawObject o;
std::vector<float> vb; // pos(3float), normal(3float), color(3float)
@@ -241,15 +337,15 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n
for (size_t v = 0; v < attrib.face_num_verts.size(); v++) {
assert(attrib.face_num_verts[v] % 3 == 0); // assume all triangle face.
for (size_t f = 0; f < attrib.face_num_verts[v] / 3; f++) {
tinyobj_opt::vertex_index idx0 = attrib.faces[face_offset+3*f+0];
tinyobj_opt::vertex_index idx1 = attrib.faces[face_offset+3*f+1];
tinyobj_opt::vertex_index idx2 = attrib.faces[face_offset+3*f+2];
tinyobj_opt::index_t idx0 = attrib.indices[face_offset+3*f+0];
tinyobj_opt::index_t idx1 = attrib.indices[face_offset+3*f+1];
tinyobj_opt::index_t idx2 = attrib.indices[face_offset+3*f+2];
float v[3][3];
for (int k = 0; k < 3; k++) {
int f0 = idx0.v_idx;
int f1 = idx1.v_idx;
int f2 = idx2.v_idx;
int f0 = idx0.vertex_index;
int f1 = idx1.vertex_index;
int f2 = idx2.vertex_index;
assert(f0 >= 0);
assert(f1 >= 0);
assert(f2 >= 0);
@@ -268,19 +364,24 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n
float n[3][3];
if (attrib.normals.size() > 0) {
int f0 = idx0.vn_idx;
int f1 = idx1.vn_idx;
int f2 = idx2.vn_idx;
assert(f0 >= 0);
assert(f1 >= 0);
assert(f2 >= 0);
assert(3*f0+2 < attrib.normals.size());
assert(3*f1+2 < attrib.normals.size());
assert(3*f2+2 < attrib.normals.size());
int nf0 = idx0.normal_index;
int nf1 = idx1.normal_index;
int nf2 = idx2.normal_index;
if (nf0 >= 0 && nf1 >= 0 && nf2 >= 0) {
assert(3*nf0+2 < attrib.normals.size());
assert(3*nf1+2 < attrib.normals.size());
assert(3*nf2+2 < attrib.normals.size());
for (int k = 0; k < 3; k++) {
n[0][k] = attrib.normals[3*f0+k];
n[1][k] = attrib.normals[3*f1+k];
n[2][k] = attrib.normals[3*f2+k];
n[0][k] = attrib.normals[3*nf0+k];
n[1][k] = attrib.normals[3*nf1+k];
n[2][k] = attrib.normals[3*nf2+k];
}
} else {
// compute geometric normal
CalcNormal(n[0], v[0], v[1], v[2]);
n[1][0] = n[0][0]; n[1][1] = n[0][1]; n[1][2] = n[0][2];
n[2][0] = n[0][0]; n[2][1] = n[0][1]; n[2][2] = n[0][2];
}
} else {
// compute geometric normal
@@ -299,7 +400,7 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n
// Use normal as color.
float c[3] = {n[k][0], n[k][1], n[k][2]};
float len2 = c[0] * c[0] + c[1] * c[1] + c[2] * c[2];
if (len2 > 0.0f) {
if (len2 > 1.0e-6f) {
float len = sqrtf(len2);
c[0] /= len;
@@ -330,9 +431,6 @@ bool LoadObjAndConvert(float bmin[3], float bmax[3], const char* filename, int n
printf("bmax = %f, %f, %f\n", bmax[0], bmax[1], bmax[2]);
return true;
#else
return false;
#endif
}
void reshapeFunc(GLFWwindow* window, int w, int h)
@@ -499,35 +597,45 @@ static void Init() {
int main(int argc, char **argv)
{
if (argc < 2) {
std::cout << "Needs input.obj\n" << std::endl;
std::cout << "view input.obj <num_threads> <benchark_only> <verbose>" << std::endl;
return 0;
}
bool benchmark_only = false;
int num_threads = -1;
bool verbose = false;
if (argc > 2) {
num_threads = atoi(argv[2]);
}
if (argc > 3) {
benchmark_only = true;
benchmark_only = (atoi(argv[3]) > 0) ? true : false;
}
if (argc > 4) {
verbose = true;
}
if (benchmark_only) {
tinyobj_opt::attrib_t attrib;
std::vector<tinyobj_opt::shape_t> shapes;
std::vector<tinyobj_opt::material_t> materials;
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;
}
printf("filesize: %d\n", (int)data_len);
tinyobj_opt::LoadOption option;
option.req_num_threads = num_threads;
bool ret = parseObj(&attrib, &shapes, data, data_len, option);
option.verbose = true;
bool ret = parseObj(&attrib, &shapes, &materials, data, data_len, option);
return ret;
}
@@ -569,7 +677,7 @@ int main(int argc, char **argv)
reshapeFunc(window, width, height);
float bmin[3], bmax[3];
if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads)) {
if (false == LoadObjAndConvert(bmin, bmax, argv[1], num_threads, verbose)) {
printf("failed to load & conv\n");
return -1;
}

View File

@@ -4,19 +4,19 @@
#define TINYOBJLOADER_IMPLEMENTATION
#include "tiny_obj_loader.h"
#include <cassert>
#include <cstdio>
#include <cstdlib>
#include <cassert>
#include <fstream>
#include <iostream>
#include <sstream>
#include <fstream>
#ifdef _WIN32
#ifdef __cplusplus
extern "C" {
#endif
#include <windows.h>
#include <mmsystem.h>
#include <windows.h>
#ifdef __cplusplus
}
#endif
@@ -30,7 +30,7 @@ extern "C" {
#endif
class timerutil {
public:
public:
#ifdef _WIN32
typedef DWORD time_t;
@@ -58,7 +58,8 @@ public:
static_cast<time_t>((tv[1].tv_usec - tv[0].tv_usec) / 1000);
}
time_t usec() {
return this->sec() * 1000000 + static_cast<time_t>(tv[1].tv_usec - tv[0].tv_usec);
return this->sec() * 1000000 +
static_cast<time_t>(tv[1].tv_usec - tv[0].tv_usec);
}
time_t current() {
struct timeval t;
@@ -81,7 +82,7 @@ public:
#endif
#endif
private:
private:
#ifdef _WIN32
DWORD t_[2];
#else
@@ -94,94 +95,101 @@ private:
#endif
};
static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector<tinyobj::shape_t>& shapes, const std::vector<tinyobj::material_t>& materials)
{
static void PrintInfo(const tinyobj::attrib_t& attrib,
const std::vector<tinyobj::shape_t>& shapes,
const std::vector<tinyobj::material_t>& materials) {
std::cout << "# of vertices : " << (attrib.vertices.size() / 3) << std::endl;
std::cout << "# of normals : " << (attrib.normals.size() / 3) << std::endl;
std::cout << "# of texcoords : " << (attrib.texcoords.size() / 2) << std::endl;
std::cout << "# of texcoords : " << (attrib.texcoords.size() / 2)
<< std::endl;
std::cout << "# of shapes : " << shapes.size() << std::endl;
std::cout << "# of materials : " << materials.size() << std::endl;
for (size_t v = 0; v < attrib.vertices.size() / 3; v++) {
printf(" v[%ld] = (%f, %f, %f)\n", static_cast<long>(v),
static_cast<const double>(attrib.vertices[3*v+0]),
static_cast<const double>(attrib.vertices[3*v+1]),
static_cast<const double>(attrib.vertices[3*v+2]));
static_cast<const double>(attrib.vertices[3 * v + 0]),
static_cast<const double>(attrib.vertices[3 * v + 1]),
static_cast<const double>(attrib.vertices[3 * v + 2]));
}
for (size_t v = 0; v < attrib.normals.size() / 3; v++) {
printf(" n[%ld] = (%f, %f, %f)\n", static_cast<long>(v),
static_cast<const double>(attrib.normals[3*v+0]),
static_cast<const double>(attrib.normals[3*v+1]),
static_cast<const double>(attrib.normals[3*v+2]));
static_cast<const double>(attrib.normals[3 * v + 0]),
static_cast<const double>(attrib.normals[3 * v + 1]),
static_cast<const double>(attrib.normals[3 * v + 2]));
}
for (size_t v = 0; v < attrib.texcoords.size() / 2; v++) {
printf(" uv[%ld] = (%f, %f)\n", static_cast<long>(v),
static_cast<const double>(attrib.texcoords[2*v+0]),
static_cast<const double>(attrib.texcoords[2*v+1]));
static_cast<const double>(attrib.texcoords[2 * v + 0]),
static_cast<const double>(attrib.texcoords[2 * v + 1]));
}
// For each shape
for (size_t i = 0; i < shapes.size(); i++) {
printf("shape[%ld].name = %s\n", static_cast<long>(i), shapes[i].name.c_str());
printf("Size of shape[%ld].indices: %lu\n", static_cast<long>(i), static_cast<unsigned long>(shapes[i].mesh.indices.size()));
printf("shape[%ld].name = %s\n", static_cast<long>(i),
shapes[i].name.c_str());
printf("Size of shape[%ld].indices: %lu\n", static_cast<long>(i),
static_cast<unsigned long>(shapes[i].mesh.indices.size()));
size_t index_offset = 0;
assert(shapes[i].mesh.num_face_vertices.size() == shapes[i].mesh.material_ids.size());
assert(shapes[i].mesh.num_face_vertices.size() ==
shapes[i].mesh.material_ids.size());
printf("shape[%ld].num_faces: %lu\n", static_cast<long>(i), static_cast<unsigned long>(shapes[i].mesh.num_face_vertices.size()));
printf("shape[%ld].num_faces: %lu\n", static_cast<long>(i),
static_cast<unsigned long>(shapes[i].mesh.num_face_vertices.size()));
// For each face
for (size_t f = 0; f < shapes[i].mesh.num_face_vertices.size(); f++) {
size_t fnum = shapes[i].mesh.num_face_vertices[f];
printf(" face[%ld].fnum = %ld\n", static_cast<long>(f), static_cast<unsigned long>(fnum));
printf(" face[%ld].fnum = %ld\n", static_cast<long>(f),
static_cast<unsigned long>(fnum));
// For each vertex in the face
for (size_t v = 0; v < fnum; v++) {
tinyobj::index_t idx = shapes[i].mesh.indices[index_offset + v];
printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast<long>(f), static_cast<long>(v), idx.vertex_index, idx.normal_index, idx.texcoord_index);
printf(" face[%ld].v[%ld].idx = %d/%d/%d\n", static_cast<long>(f),
static_cast<long>(v), idx.vertex_index, idx.normal_index,
idx.texcoord_index);
}
printf(" face[%ld].material_id = %d\n", static_cast<long>(f), shapes[i].mesh.material_ids[f]);
printf(" face[%ld].material_id = %d\n", static_cast<long>(f),
shapes[i].mesh.material_ids[f]);
index_offset += fnum;
}
printf("shape[%ld].num_tags: %lu\n", static_cast<long>(i), static_cast<unsigned long>(shapes[i].mesh.tags.size()));
printf("shape[%ld].num_tags: %lu\n", static_cast<long>(i),
static_cast<unsigned long>(shapes[i].mesh.tags.size()));
for (size_t t = 0; t < shapes[i].mesh.tags.size(); t++) {
printf(" tag[%ld] = %s ", static_cast<long>(t), shapes[i].mesh.tags[t].name.c_str());
printf(" tag[%ld] = %s ", static_cast<long>(t),
shapes[i].mesh.tags[t].name.c_str());
printf(" ints: [");
for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j)
{
for (size_t j = 0; j < shapes[i].mesh.tags[t].intValues.size(); ++j) {
printf("%ld", static_cast<long>(shapes[i].mesh.tags[t].intValues[j]));
if (j < (shapes[i].mesh.tags[t].intValues.size()-1))
{
if (j < (shapes[i].mesh.tags[t].intValues.size() - 1)) {
printf(", ");
}
}
printf("]");
printf(" floats: [");
for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j)
{
printf("%f", static_cast<const double>(shapes[i].mesh.tags[t].floatValues[j]));
if (j < (shapes[i].mesh.tags[t].floatValues.size()-1))
{
for (size_t j = 0; j < shapes[i].mesh.tags[t].floatValues.size(); ++j) {
printf("%f", static_cast<const double>(
shapes[i].mesh.tags[t].floatValues[j]));
if (j < (shapes[i].mesh.tags[t].floatValues.size() - 1)) {
printf(", ");
}
}
printf("]");
printf(" strings: [");
for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j)
{
for (size_t j = 0; j < shapes[i].mesh.tags[t].stringValues.size(); ++j) {
printf("%s", shapes[i].mesh.tags[t].stringValues[j].c_str());
if (j < (shapes[i].mesh.tags[t].stringValues.size()-1))
{
if (j < (shapes[i].mesh.tags[t].stringValues.size() - 1)) {
printf(", ");
}
}
@@ -191,25 +199,59 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector<tinyobj
}
for (size_t i = 0; i < materials.size(); i++) {
printf("material[%ld].name = %s\n", static_cast<long>(i), materials[i].name.c_str());
printf(" material.Ka = (%f, %f ,%f)\n", static_cast<const double>(materials[i].ambient[0]), static_cast<const double>(materials[i].ambient[1]), static_cast<const double>(materials[i].ambient[2]));
printf(" material.Kd = (%f, %f ,%f)\n", static_cast<const double>(materials[i].diffuse[0]), static_cast<const double>(materials[i].diffuse[1]), static_cast<const double>(materials[i].diffuse[2]));
printf(" material.Ks = (%f, %f ,%f)\n", static_cast<const double>(materials[i].specular[0]), static_cast<const double>(materials[i].specular[1]), static_cast<const double>(materials[i].specular[2]));
printf(" material.Tr = (%f, %f ,%f)\n", static_cast<const double>(materials[i].transmittance[0]), static_cast<const double>(materials[i].transmittance[1]), static_cast<const double>(materials[i].transmittance[2]));
printf(" material.Ke = (%f, %f ,%f)\n", static_cast<const double>(materials[i].emission[0]), static_cast<const double>(materials[i].emission[1]), static_cast<const double>(materials[i].emission[2]));
printf(" material.Ns = %f\n", static_cast<const double>(materials[i].shininess));
printf("material[%ld].name = %s\n", static_cast<long>(i),
materials[i].name.c_str());
printf(" material.Ka = (%f, %f ,%f)\n",
static_cast<const double>(materials[i].ambient[0]),
static_cast<const double>(materials[i].ambient[1]),
static_cast<const double>(materials[i].ambient[2]));
printf(" material.Kd = (%f, %f ,%f)\n",
static_cast<const double>(materials[i].diffuse[0]),
static_cast<const double>(materials[i].diffuse[1]),
static_cast<const double>(materials[i].diffuse[2]));
printf(" material.Ks = (%f, %f ,%f)\n",
static_cast<const double>(materials[i].specular[0]),
static_cast<const double>(materials[i].specular[1]),
static_cast<const double>(materials[i].specular[2]));
printf(" material.Tr = (%f, %f ,%f)\n",
static_cast<const double>(materials[i].transmittance[0]),
static_cast<const double>(materials[i].transmittance[1]),
static_cast<const double>(materials[i].transmittance[2]));
printf(" material.Ke = (%f, %f ,%f)\n",
static_cast<const double>(materials[i].emission[0]),
static_cast<const double>(materials[i].emission[1]),
static_cast<const double>(materials[i].emission[2]));
printf(" material.Ns = %f\n",
static_cast<const double>(materials[i].shininess));
printf(" material.Ni = %f\n", static_cast<const double>(materials[i].ior));
printf(" material.dissolve = %f\n", static_cast<const double>(materials[i].dissolve));
printf(" material.dissolve = %f\n",
static_cast<const double>(materials[i].dissolve));
printf(" material.illum = %d\n", materials[i].illum);
printf(" material.map_Ka = %s\n", materials[i].ambient_texname.c_str());
printf(" material.map_Kd = %s\n", materials[i].diffuse_texname.c_str());
printf(" material.map_Ks = %s\n", materials[i].specular_texname.c_str());
printf(" material.map_Ns = %s\n", materials[i].specular_highlight_texname.c_str());
printf(" material.map_Ns = %s\n",
materials[i].specular_highlight_texname.c_str());
printf(" material.map_bump = %s\n", materials[i].bump_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());
std::map<std::string, std::string>::const_iterator it(materials[i].unknown_parameter.begin());
std::map<std::string, std::string>::const_iterator itEnd(materials[i].unknown_parameter.end());
printf(" <<PBR>>\n");
printf(" material.Pr = %f\n", materials[i].roughness);
printf(" material.Pm = %f\n", materials[i].metallic);
printf(" material.Ps = %f\n", materials[i].sheen);
printf(" material.Pc = %f\n", materials[i].clearcoat_thickness);
printf(" material.Pcr = %f\n", materials[i].clearcoat_thickness);
printf(" material.aniso = %f\n", materials[i].anisotropy);
printf(" material.anisor = %f\n", materials[i].anisotropy_rotation);
printf(" material.map_Ke = %s\n", materials[i].emissive_texname.c_str());
printf(" material.map_Pr = %s\n", materials[i].roughness_texname.c_str());
printf(" material.map_Pm = %s\n", materials[i].metallic_texname.c_str());
printf(" material.map_Ps = %s\n", materials[i].sheen_texname.c_str());
printf(" material.norm = %s\n", materials[i].normal_texname.c_str());
std::map<std::string, std::string>::const_iterator it(
materials[i].unknown_parameter.begin());
std::map<std::string, std::string>::const_iterator itEnd(
materials[i].unknown_parameter.end());
for (; it != itEnd; it++) {
printf(" material.%s = %s\n", it->first.c_str(), it->second.c_str());
@@ -218,12 +260,8 @@ static void PrintInfo(const tinyobj::attrib_t &attrib, const std::vector<tinyobj
}
}
static bool
TestLoadObj(
const char* filename,
const char* basepath = NULL,
bool triangulate = true)
{
static bool TestLoadObj(const char* filename, const char* basepath = NULL,
bool triangulate = true) {
std::cout << "Loading " << filename << std::endl;
tinyobj::attrib_t attrib;
@@ -233,7 +271,8 @@ TestLoadObj(
timerutil t;
t.start();
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename, basepath, triangulate);
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename,
basepath, triangulate);
t.end();
printf("Parsing time: %lu [msecs]\n", t.msec());
@@ -251,15 +290,11 @@ TestLoadObj(
return true;
}
static bool
TestStreamLoadObj()
{
static bool TestStreamLoadObj() {
std::cout << "Stream Loading " << std::endl;
std::stringstream objStream;
objStream
<< "mtllib cube.mtl\n"
objStream << "mtllib cube.mtl\n"
"\n"
"v 0.000000 2.000000 2.000000\n"
"v 0.000000 0.000000 2.000000\n"
@@ -291,7 +326,7 @@ TestStreamLoadObj()
"f 2 6 7 3\n"
"# 6 elements";
std::string matStream(
std::string matStream(
"newmtl white\n"
"Ka 0 0 0\n"
"Kd 1 1 1\n"
@@ -318,18 +353,15 @@ std::string matStream(
"Ks 0 0 0");
using namespace tinyobj;
class MaterialStringStreamReader:
public MaterialReader
{
class MaterialStringStreamReader : public MaterialReader {
public:
MaterialStringStreamReader(const std::string& matSStream): m_matSStream(matSStream) {}
MaterialStringStreamReader(const std::string& matSStream)
: m_matSStream(matSStream) {}
virtual ~MaterialStringStreamReader() {}
virtual bool operator() (
const std::string& matId,
virtual bool operator()(const std::string& matId,
std::vector<material_t>* materials,
std::map<std::string, int>* matMap,
std::string* err)
{
std::string* err) {
(void)matId;
(void)err;
LoadMtl(matMap, materials, &m_matSStream);
@@ -345,7 +377,8 @@ std::string matStream(
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream, &matSSReader);
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, &objStream,
&matSSReader);
if (!err.empty()) {
std::cerr << err << std::endl;
@@ -360,11 +393,7 @@ std::string matStream(
return true;
}
int
main(
int argc,
char **argv)
{
int main(int argc, char** argv) {
if (argc > 1) {
const char* basepath = "models/";
if (argc > 2) {
@@ -372,10 +401,11 @@ main(
}
assert(true == TestLoadObj(argv[1], basepath));
} else {
//assert(true == TestLoadObj("cornell_box.obj"));
//assert(true == TestLoadObj("cube.obj"));
// assert(true == TestLoadObj("cornell_box.obj"));
// assert(true == TestLoadObj("cube.obj"));
assert(true == TestStreamLoadObj());
assert(true == TestLoadObj("models/catmark_torus_creases0.obj", "models/", false));
assert(true ==
TestLoadObj("models/catmark_torus_creases0.obj", "models/", false));
}
return 0;

6
models/issue-92.mtl Normal file
View File

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

7
models/issue-92.obj Normal file
View File

@@ -0,0 +1,7 @@
mtllib issue-92.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

5
models/issue-95-2.mtl Normal file
View File

@@ -0,0 +1,5 @@
newmtl default
Ka 0 0 0
Kd 0 0 0
Ks 0 0 0
Tf 0.1 0.2 0.3

7
models/issue-95-2.obj Normal file
View File

@@ -0,0 +1,7 @@
mtllib issue-95-2.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

5
models/issue-95.mtl Normal file
View File

@@ -0,0 +1,5 @@
newmtl default
Ka 0 0 0
Kd 0 0 0
Ks 0 0 0
Kt 0.1 0.2 0.3

7
models/issue-95.obj Normal file
View File

@@ -0,0 +1,7 @@
mtllib issue-95.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

19
models/pbr-mat-ext.mtl Normal file
View File

@@ -0,0 +1,19 @@
# .MTL with PBR extension.
newmtl pbr
Ka 0 0 0
Kd 1 1 1
Ks 0 0 0
Ke 0.1 0.1 0.1
Pr 0.2
Pm 0.3
Ps 0.4
Pc 0.5
Pcr 0.6
aniso 0.7
anisor 0.8
map_Pr roughness.tex
map_Pm metallic.tex
map_Ps sheen.tex
map_Ke emissive.tex
norm normalmap.tex

10
models/pbr-mat-ext.obj Normal file
View File

@@ -0,0 +1,10 @@
mtllib pbr-mat-ext.mtl
o floor
usemtl pbr
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
f 1 2 3 4

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

3
python/TODO.md Normal file
View File

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

View File

@@ -1,127 +1,170 @@
//python3 module for tinyobjloader
// python3 module for tinyobjloader
//
//usage:
// usage:
// import tinyobjloader as tol
// model = tol.LoadObj(name)
// print(model["shapes"])
// print(model["materials"]
// note:
// `shape.mesh.index_t` is represented as flattened array: (vertex_index, normal_index, texcoord_index) * num_faces
#include <Python.h>
#include <vector>
#include "../tiny_obj_loader.h"
typedef std::vector<double> vectd;
typedef std::vector<int> vecti;
PyObject*
pyTupleFromfloat3 (float array[3])
{
PyObject* pyTupleFromfloat3(float array[3]) {
int i;
PyObject* tuple = PyTuple_New(3);
for(i=0; i<=2 ; i++){
for (i = 0; i <= 2; i++) {
PyTuple_SetItem(tuple, i, PyFloat_FromDouble(array[i]));
}
return tuple;
}
extern "C"
{
extern "C" {
static PyObject*
pyLoadObj(PyObject* self, PyObject* args)
{
PyObject *rtndict, *pyshapes, *pymaterials,
*current, *meshobj;
static PyObject* pyLoadObj(PyObject* self, PyObject* args) {
PyObject *rtndict, *pyshapes, *pymaterials, *attribobj, *current, *meshobj;
char const* current_name;
char const* filename;
char *current_name;
vectd vect;
std::vector<tinyobj::index_t> indices;
std::vector<unsigned char> face_verts;
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
if(!PyArg_ParseTuple(args, "s", &filename))
return NULL;
if (!PyArg_ParseTuple(args, "s", &filename)) return NULL;
std::string err;
tinyobj::LoadObj(shapes, materials, err, filename);
tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename);
pyshapes = PyDict_New();
pymaterials = PyDict_New();
rtndict = PyDict_New();
for (std::vector<tinyobj::shape_t>::iterator shape = shapes.begin() ;
shape != shapes.end(); shape++)
{
meshobj = PyDict_New();
tinyobj::mesh_t cm = (*shape).mesh;
attribobj = PyDict_New();
for (int i = 0; i <= 4; i++ )
{
for (int i = 0; i <= 2; i++) {
current = PyList_New(0);
switch(i) {
switch (i) {
case 0:
current_name = "positions";
vect = vectd(cm.positions.begin(), cm.positions.end()); break;
current_name = "vertices";
vect = vectd(attrib.vertices.begin(), attrib.vertices.end());
break;
case 1:
current_name = "normals";
vect = vectd(cm.normals.begin(), cm.normals.end()); break;
vect = vectd(attrib.normals.begin(), attrib.normals.end());
break;
case 2:
current_name = "texcoords";
vect = vectd(cm.texcoords.begin(), cm.texcoords.end()); break;
case 3:
current_name = "indicies";
vect = vectd(cm.indices.begin(), cm.indices.end()); break;
case 4:
current_name = "material_ids";
vect = vectd(cm.material_ids.begin(), cm.material_ids.end()); break;
vect = vectd(attrib.texcoords.begin(), attrib.texcoords.end());
break;
}
for (vectd::iterator it = vect.begin() ;
it != vect.end(); it++)
{
for (vectd::iterator it = vect.begin(); it != vect.end(); it++) {
PyList_Insert(current, it - vect.begin(), PyFloat_FromDouble(*it));
}
PyDict_SetItemString(meshobj, current_name, current);
PyDict_SetItemString(attribobj, current_name, current);
}
for (std::vector<tinyobj::shape_t>::iterator shape = shapes.begin();
shape != shapes.end(); shape++) {
meshobj = PyDict_New();
tinyobj::mesh_t cm = (*shape).mesh;
{
current = PyList_New(0);
for (size_t i = 0; i < cm.indices.size(); i++) {
// Flatten index array: v_idx, vn_idx, vt_idx, v_idx, vn_idx, vt_idx,
// ...
PyList_Insert(current, 3 * i + 0,
PyLong_FromLong(cm.indices[i].vertex_index));
PyList_Insert(current, 3 * i + 1,
PyLong_FromLong(cm.indices[i].normal_index));
PyList_Insert(current, 3 * i + 2,
PyLong_FromLong(cm.indices[i].texcoord_index));
}
PyDict_SetItemString(meshobj, "indices", current);
}
{
current = PyList_New(0);
for (size_t i = 0; i < cm.num_face_vertices.size(); i++) {
// Widen data type to long.
PyList_Insert(current, i, PyLong_FromLong(cm.num_face_vertices[i]));
}
PyDict_SetItemString(meshobj, "num_face_vertices", current);
}
{
current = PyList_New(0);
for (size_t i = 0; i < cm.material_ids.size(); i++) {
PyList_Insert(current, i, PyLong_FromLong(cm.material_ids[i]));
}
PyDict_SetItemString(meshobj, "material_ids", current);
}
PyDict_SetItemString(pyshapes, (*shape).name.c_str(), meshobj);
}
for (std::vector<tinyobj::material_t>::iterator mat = materials.begin() ;
mat != materials.end(); mat++)
{
PyObject *matobj = PyDict_New();
PyObject *unknown_parameter = PyDict_New();
for (std::vector<tinyobj::material_t>::iterator mat = materials.begin();
mat != materials.end(); mat++) {
PyObject* matobj = PyDict_New();
PyObject* unknown_parameter = PyDict_New();
for (std::map<std::string, std::string>::iterator p = (*mat).unknown_parameter.begin() ;
p != (*mat).unknown_parameter.end(); ++p)
{
PyDict_SetItemString(unknown_parameter, p->first.c_str(), PyUnicode_FromString(p->second.c_str()));
for (std::map<std::string, std::string>::iterator p =
(*mat).unknown_parameter.begin();
p != (*mat).unknown_parameter.end(); ++p) {
PyDict_SetItemString(unknown_parameter, p->first.c_str(),
PyUnicode_FromString(p->second.c_str()));
}
PyDict_SetItemString(matobj, "shininess", PyFloat_FromDouble((*mat).shininess));
PyDict_SetItemString(matobj, "shininess",
PyFloat_FromDouble((*mat).shininess));
PyDict_SetItemString(matobj, "ior", PyFloat_FromDouble((*mat).ior));
PyDict_SetItemString(matobj, "dissolve", PyFloat_FromDouble((*mat).dissolve));
PyDict_SetItemString(matobj, "dissolve",
PyFloat_FromDouble((*mat).dissolve));
PyDict_SetItemString(matobj, "illum", PyLong_FromLong((*mat).illum));
PyDict_SetItemString(matobj, "ambient_texname", PyUnicode_FromString((*mat).ambient_texname.c_str()));
PyDict_SetItemString(matobj, "diffuse_texname", PyUnicode_FromString((*mat).diffuse_texname.c_str()));
PyDict_SetItemString(matobj, "specular_texname", PyUnicode_FromString((*mat).specular_texname.c_str()));
PyDict_SetItemString(matobj, "specular_highlight_texname", PyUnicode_FromString((*mat).specular_highlight_texname.c_str()));
PyDict_SetItemString(matobj, "bump_texname", PyUnicode_FromString((*mat).bump_texname.c_str()));
PyDict_SetItemString(matobj, "displacement_texname", PyUnicode_FromString((*mat).displacement_texname.c_str()));
PyDict_SetItemString(matobj, "alpha_texname", PyUnicode_FromString((*mat).alpha_texname.c_str()));
PyDict_SetItemString(matobj, "ambient_texname",
PyUnicode_FromString((*mat).ambient_texname.c_str()));
PyDict_SetItemString(matobj, "diffuse_texname",
PyUnicode_FromString((*mat).diffuse_texname.c_str()));
PyDict_SetItemString(matobj, "specular_texname",
PyUnicode_FromString((*mat).specular_texname.c_str()));
PyDict_SetItemString(
matobj, "specular_highlight_texname",
PyUnicode_FromString((*mat).specular_highlight_texname.c_str()));
PyDict_SetItemString(matobj, "bump_texname",
PyUnicode_FromString((*mat).bump_texname.c_str()));
PyDict_SetItemString(
matobj, "displacement_texname",
PyUnicode_FromString((*mat).displacement_texname.c_str()));
PyDict_SetItemString(matobj, "alpha_texname",
PyUnicode_FromString((*mat).alpha_texname.c_str()));
PyDict_SetItemString(matobj, "ambient", pyTupleFromfloat3((*mat).ambient));
PyDict_SetItemString(matobj, "diffuse", pyTupleFromfloat3((*mat).diffuse));
PyDict_SetItemString(matobj, "specular", pyTupleFromfloat3((*mat).specular));
PyDict_SetItemString(matobj, "transmittance", pyTupleFromfloat3((*mat).transmittance));
PyDict_SetItemString(matobj, "emission", pyTupleFromfloat3((*mat).emission));
PyDict_SetItemString(matobj, "specular",
pyTupleFromfloat3((*mat).specular));
PyDict_SetItemString(matobj, "transmittance",
pyTupleFromfloat3((*mat).transmittance));
PyDict_SetItemString(matobj, "emission",
pyTupleFromfloat3((*mat).emission));
PyDict_SetItemString(matobj, "unknown_parameter", unknown_parameter);
PyDict_SetItemString(pymaterials, (*mat).name.c_str(), matobj);
@@ -133,28 +176,16 @@ pyLoadObj(PyObject* self, PyObject* args)
return rtndict;
}
static PyMethodDef mMethods[] = {
{"LoadObj", pyLoadObj, METH_VARARGS},
{NULL, NULL, 0, NULL}
{"LoadObj", pyLoadObj, METH_VARARGS}, {NULL, NULL, 0, NULL}
};
static struct PyModuleDef moduledef = {PyModuleDef_HEAD_INIT, "tinyobjloader",
NULL, -1, mMethods};
static struct PyModuleDef moduledef = {
PyModuleDef_HEAD_INIT,
"tinyobjloader",
NULL,
-1,
mMethods
};
PyMODINIT_FUNC
PyInit_tinyobjloader(void)
{
PyMODINIT_FUNC PyInit_tinyobjloader(void) {
return PyModule_Create(&moduledef);
}
}

View File

@@ -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()
@@ -293,7 +335,7 @@ std::string matStream(
return true;
}
const char* gMtlBasePath = "../models";
const char* gMtlBasePath = "../models/";
TEST_CASE("cornell_box", "[Loader]") {
@@ -319,10 +361,133 @@ TEST_CASE("catmark_torus_creases0", "[Loader]") {
REQUIRE(8 == shapes[0].mesh.tags.size());
}
TEST_CASE("pbr", "[Loader]") {
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/pbr-mat-ext.obj", gMtlBasePath, /*triangulate*/false);
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(1 == materials.size());
REQUIRE(0.2 == Approx(materials[0].roughness));
REQUIRE(0.3 == Approx(materials[0].metallic));
REQUIRE(0.4 == Approx(materials[0].sheen));
REQUIRE(0.5 == Approx(materials[0].clearcoat_thickness));
REQUIRE(0.6 == Approx(materials[0].clearcoat_roughness));
REQUIRE(0.7 == Approx(materials[0].anisotropy));
REQUIRE(0.8 == Approx(materials[0].anisotropy_rotation));
REQUIRE(0 == materials[0].roughness_texname.compare("roughness.tex"));
REQUIRE(0 == materials[0].metallic_texname.compare("metallic.tex"));
REQUIRE(0 == materials[0].sheen_texname.compare("sheen.tex"));
REQUIRE(0 == materials[0].emissive_texname.compare("emissive.tex"));
REQUIRE(0 == materials[0].normal_texname.compare("normalmap.tex"));
}
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;
std::vector<tinyobj::material_t> materials;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-92.obj", gMtlBasePath);
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(1 == materials.size());
REQUIRE(0 == materials[0].diffuse_texname.compare("tmp.png"));
}
TEST_CASE("transmittance_filter", "[Issue95]") {
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/issue-95.obj", gMtlBasePath);
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(1 == materials.size());
REQUIRE(0.1 == Approx(materials[0].transmittance[0]));
REQUIRE(0.2 == Approx(materials[0].transmittance[1]));
REQUIRE(0.3 == Approx(materials[0].transmittance[2]));
}
TEST_CASE("transmittance_filter_Tf", "[Issue95-Tf]") {
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/issue-95-2.obj", gMtlBasePath);
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(1 == materials.size());
REQUIRE(0.1 == Approx(materials[0].transmittance[0]));
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;
std::vector<tinyobj::material_t> materials;
std::string err;
bool ret = tinyobj::LoadObj(&attrib, &shapes, &materials, &err, "../models/issue-95.obj", gMtlBasePath);
if (!err.empty()) {
std::cerr << err << std::endl;
}
REQUIRE(true == ret);
REQUIRE(1 == materials.size());
REQUIRE(0.1 == Approx(materials[0].transmittance[0]));
REQUIRE(0.2 == Approx(materials[0].transmittance[1]));
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(

View File

@@ -23,34 +23,8 @@ 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.
// Support different index for
// vertex/normal/texcoord(#73, #39)
// version 0.9.20: Fixes creating per-face material using `usemtl`(#68)
// version 0.9.17: Support n-polygon and crease tag(OpenSubdiv extension)
// version 0.9.16: Make tinyobjloader header-only
// version 0.9.15: Change API to handle no mtl file case correctly(#58)
// version 0.9.14: Support specular highlight, bump, displacement and alpha
// map(#53)
// version 0.9.13: Report "Material file not found message" in `err`(#46)
// version 0.9.12: Fix groups being ignored if they have 'usemtl' just before
// 'g' (#44)
// version 0.9.11: Invert `Tr` parameter(#43)
// version 0.9.10: Fix seg fault on windows.
// version 0.9.9 : Replace atof() with custom parser.
// version 0.9.8 : Fix multi-materials(per-face material ID).
// version 0.9.7 : Support multi-materials(per-face material ID) per
// object/group.
// version 0.9.6 : Support Ni(index of refraction) mtl parameter.
// Parse transmittance material parameter correctly.
// version 0.9.5 : Parse multiple group name.
// Add support of specifying the base path to load material
// file.
// version 0.9.4 : Initial support of group tag(g)
// version 0.9.3 : Fix parsing triple 'x/y/z'
// version 0.9.2 : Add more .mtl load support
// version 0.9.1 : Add initial .mtl load support
// version 0.9.0 : Initial
//
//
@@ -62,9 +36,9 @@ THE SOFTWARE.
#ifndef TINY_OBJ_LOADER_H_
#define TINY_OBJ_LOADER_H_
#include <map>
#include <string>
#include <vector>
#include <map>
namespace tinyobj {
@@ -91,6 +65,22 @@ typedef struct {
std::string bump_texname; // map_bump, bump
std::string displacement_texname; // disp
std::string alpha_texname; // map_d
// PBR extension
// http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr
float roughness; // [0, 1] default 0
float metallic; // [0, 1] default 0
float sheen; // [0, 1] default 0
float clearcoat_thickness; // [0, 1] default 0
float clearcoat_roughness; // [0, 1] default 0
float anisotropy; // aniso. [0, 1] default 0
float anisotropy_rotation; // anisor. [0, 1] default 0
std::string roughness_texname; // map_Pr
std::string metallic_texname; // map_Pm
std::string sheen_texname; // map_Ps
std::string emissive_texname; // map_Ke
std::string normal_texname; // norm. For normal mapping.
std::map<std::string, std::string> unknown_parameter;
} material_t;
@@ -102,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;
@@ -112,8 +102,9 @@ typedef struct {
typedef struct {
std::vector<index_t> indices;
std::vector<unsigned char>
num_face_vertices; // The number of vertices per face. 3 = polygon, 4 = quad, ... Up to 255.
std::vector<unsigned char> num_face_vertices; // The number of vertices per
// face. 3 = polygon, 4 = quad,
// ... Up to 255.
std::vector<int> material_ids; // per-face material ID
std::vector<tag_t> tags; // SubD tag
} mesh_t;
@@ -131,14 +122,22 @@ typedef struct {
} attrib_t;
typedef struct callback_t_ {
void (*vertex_cb)(void *user_data, float x, float y, float z);
// W is optional and set to 1 if there is no `w` item in `v` line
void (*vertex_cb)(void *user_data, float x, float y, float z, float w);
void (*normal_cb)(void *user_data, float x, float y, float z);
void (*texcoord_cb)(void *user_data, float x, float y);
// -2147483648 will be passed for undefined index
void (*index_cb)(void *user_data, int v_idx, int vn_idx, int vt_idx);
// `name` material name, `materialId` = the array index of material_t[]. -1 if
// y and z are optional and set to 0 if there is no `y` and/or `z` item(s) in
// `vt` line.
void (*texcoord_cb)(void *user_data, float x, float y, float z);
// called per 'f' line. num_indices is the number of face indices(e.g. 3 for
// triangle, 4 for quad)
// 0 will be passed for undefined index in index_t members.
void (*index_cb)(void *user_data, index_t *indices, int num_indices);
// `name` material name, `material_id` = the array index of material_t[]. -1
// if
// a material not found in .mtl
void (*usemtl_cb)(void *user_data, const char *name, int materialId);
void (*usemtl_cb)(void *user_data, const char *name, int material_id);
// `materials` = parsed material data.
void (*mtllib_cb)(void *user_data, const material_t *materials,
int num_materials);
@@ -155,7 +154,6 @@ typedef struct callback_t_ {
mtllib_cb(NULL),
group_cb(NULL),
object_cb(NULL) {}
} callback_t;
class MaterialReader {
@@ -182,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
@@ -200,12 +211,11 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
/// `callback.mtllib_cb`.
/// Returns true when loading .obj/.mtl become success.
/// Returns warning and error message into `err`
/// 'mtl_basepath' is optional, and used for base path for .mtl file.
/// 'triangulate' is optional, and used whether triangulate polygon face in .obj
/// or not.
bool LoadObjWithCallback(void *user_data, const callback_t &callback,
std::string *err, std::istream *inStream,
MaterialReader *readMatFn);
/// See `examples/callback_api/` for how to use this function.
bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
void *user_data = NULL,
MaterialReader *readMatFn = NULL,
std::string *err = NULL);
/// Loads object from a std::istream, uses GetMtlIStreamFn to retrieve
/// std::istream for materials.
@@ -213,7 +223,7 @@ bool LoadObjWithCallback(void *user_data, 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
@@ -223,12 +233,12 @@ void LoadMtl(std::map<std::string, int> *material_map,
} // namespace tinyobj
#ifdef TINYOBJLOADER_IMPLEMENTATION
#include <cstdlib>
#include <cstring>
#include <cassert>
#include <cctype>
#include <cmath>
#include <cstddef>
#include <cctype>
#include <cstdlib>
#include <cstring>
#include <utility>
#include <fstream>
@@ -261,6 +271,38 @@ struct obj_shape {
std::vector<float> vt;
};
// See
// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf
static std::istream &safeGetline(std::istream &is, std::string &t) {
t.clear();
// The characters in the stream are read one-by-one using a std::streambuf.
// That is faster than reading them one-by-one using the std::istream.
// Code that uses streambuf this way must be guarded by a sentry object.
// The sentry object performs various tasks,
// such as thread synchronization and updating the stream state.
std::istream::sentry se(is, true);
std::streambuf *sb = is.rdbuf();
for (;;) {
int c = sb->sbumpc();
switch (c) {
case '\n':
return is;
case '\r':
if (sb->sgetc() == '\n') sb->sbumpc();
return is;
case EOF:
// Also handle the case when the last line has no line ending
if (t.empty()) is.setstate(std::ios::eofbit);
return is;
default:
t += static_cast<char>(c);
}
}
}
#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t'))
#define IS_DIGIT(x) \
(static_cast<unsigned int>((x) - '0') < static_cast<unsigned int>(10))
@@ -424,18 +466,13 @@ fail:
return false;
}
static inline float parseFloat(const char **token) {
static inline float parseFloat(const char **token, double default_value = 0.0) {
(*token) += strspn((*token), " \t");
#ifdef TINY_OBJ_LOADER_OLD_FLOAT_PARSER
float f = static_cast<float>(atof(*token));
(*token) += strcspn((*token), " \t\r");
#else
const char *end = (*token) + strcspn((*token), " \t\r");
double val = 0.0;
double val = default_value;
tryParseDouble((*token), end, &val);
float f = static_cast<float>(val);
(*token) = end;
#endif
return f;
}
@@ -451,6 +488,14 @@ static inline void parseFloat3(float *x, float *y, float *z,
(*z) = parseFloat(token);
}
static inline void parseV(float *x, float *y, float *z, float *w,
const char **token) {
(*x) = parseFloat(token);
(*y) = parseFloat(token);
(*z) = parseFloat(token);
(*w) = parseFloat(token, 1.0);
}
static tag_sizes parseTagTriple(const char **token) {
tag_sizes ts;
@@ -510,8 +555,7 @@ static vertex_index parseTriple(const char **token, int vsize, int vnsize,
// Parse raw triples: i, i/j/k, i//k, i/j
static vertex_index parseRawTriple(const char **token) {
vertex_index vi(
static_cast<int>(0x80000000)); // 0x80000000 = -2147483648 = invalid
vertex_index vi(static_cast<int>(0)); // 0 is an invalid index in OBJ
vi.v_idx = atoi((*token));
(*token) += strcspn((*token), "/ \t\r");
@@ -562,6 +606,20 @@ static void InitMaterial(material_t *material) {
material->dissolve = 1.f;
material->shininess = 1.f;
material->ior = 1.f;
material->roughness = 0.f;
material->metallic = 0.f;
material->sheen = 0.f;
material->clearcoat_thickness = 0.f;
material->clearcoat_roughness = 0.f;
material->anisotropy_rotation = 0.f;
material->anisotropy = 0.f;
material->roughness_texname = "";
material->metallic_texname = "";
material->sheen_texname = "";
material->emissive_texname = "";
material->normal_texname = "";
material->unknown_parameter.clear();
}
@@ -616,7 +674,8 @@ static bool exportFaceGroupToShape(
shape->mesh.indices.push_back(idx);
}
shape->mesh.num_face_vertices.push_back(static_cast<unsigned char>(npolys));
shape->mesh.num_face_vertices.push_back(
static_cast<unsigned char>(npolys));
shape->mesh.material_ids.push_back(material_id); // per face
}
}
@@ -633,12 +692,15 @@ void LoadMtl(std::map<std::string, int> *material_map,
material_t material;
InitMaterial(&material);
size_t maxchars = 8192; // Alloc enough size.
std::vector<char> buf(maxchars); // Alloc enough size.
while (inStream->peek() != -1) {
inStream->getline(&buf[0], static_cast<std::streamsize>(maxchars));
std::string linebuf;
std::string linebuf(&buf[0]);
safeGetline(*inStream, linebuf);
// Trim trailing whitespace.
if (linebuf.size() > 0) {
linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1);
}
// Trim newline '\r\n' or '\n'
if (linebuf.size() > 0) {
@@ -722,7 +784,8 @@ void LoadMtl(std::map<std::string, int> *material_map,
}
// transmittance
if (token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) {
if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) ||
(token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) {
token += 2;
float r, g, b;
parseFloat3(&r, &g, &b, &token);
@@ -777,6 +840,55 @@ void LoadMtl(std::map<std::string, int> *material_map,
continue;
}
// PBR: roughness
if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) {
token += 2;
material.roughness = parseFloat(&token);
continue;
}
// PBR: metallic
if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) {
token += 2;
material.metallic = parseFloat(&token);
continue;
}
// PBR: sheen
if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) {
token += 2;
material.sheen = parseFloat(&token);
continue;
}
// PBR: clearcoat thickness
if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) {
token += 2;
material.clearcoat_thickness = parseFloat(&token);
continue;
}
// PBR: clearcoat roughness
if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) {
token += 4;
material.clearcoat_roughness = parseFloat(&token);
continue;
}
// PBR: anisotropy
if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) {
token += 6;
material.anisotropy = parseFloat(&token);
continue;
}
// PBR: anisotropy rotation
if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) {
token += 7;
material.anisotropy_rotation = parseFloat(&token);
continue;
}
// ambient texture
if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) {
token += 7;
@@ -833,6 +945,41 @@ void LoadMtl(std::map<std::string, int> *material_map,
continue;
}
// PBR: roughness texture
if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) {
token += 7;
material.roughness_texname = token;
continue;
}
// PBR: metallic texture
if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) {
token += 7;
material.metallic_texname = token;
continue;
}
// PBR: sheen texture
if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) {
token += 7;
material.sheen_texname = token;
continue;
}
// PBR: emissive texture
if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) {
token += 7;
material.emissive_texname = token;
continue;
}
// PBR: normal map texture
if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) {
token += 5;
material.normal_texname = token;
continue;
}
// unknown parameter
const char *_space = strchr(token, ' ');
if (!_space) {
@@ -877,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,
@@ -909,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;
@@ -926,12 +1090,9 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
shape_t shape;
int maxchars = 8192; // Alloc enough size.
std::vector<char> buf(static_cast<size_t>(maxchars)); // Alloc enough size.
while (inStream->peek() != -1) {
inStream->getline(&buf[0], maxchars);
std::string linebuf(&buf[0]);
std::string linebuf;
safeGetline(*inStream, linebuf);
// Trim newline '\r\n' or '\n'
if (linebuf.size() > 0) {
@@ -1031,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();
@@ -1043,6 +1206,7 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
// load mtl
if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
if (readMatFn) {
char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
token += 7;
#ifdef _MSC_VER
@@ -1061,6 +1225,7 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
faceGroup.clear(); // for safety
return false;
}
}
continue;
}
@@ -1177,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
@@ -1193,21 +1362,26 @@ bool LoadObj(attrib_t *attrib, std::vector<shape_t> *shapes,
return true;
}
bool LoadObjWithCallback(void *user_data, const callback_t &callback,
std::string *err, std::istream *inStream,
MaterialReader *readMatFn) {
bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback,
void *user_data /*= NULL*/,
MaterialReader *readMatFn /*= NULL*/,
std::string *err /*= NULL*/) {
std::stringstream errss;
// material
std::map<std::string, int> material_map;
int materialId = -1; // -1 = invalid
int material_id = -1; // -1 = invalid
int maxchars = 8192; // Alloc enough size.
std::vector<char> buf(static_cast<size_t>(maxchars)); // Alloc enough size.
while (inStream->peek() != -1) {
inStream->getline(&buf[0], maxchars);
std::vector<index_t> indices;
std::vector<material_t> materials;
std::vector<std::string> names;
names.reserve(2);
std::string name;
std::vector<const char *> names_out;
std::string linebuf(&buf[0]);
std::string linebuf;
while (inStream.peek() != -1) {
safeGetline(inStream, linebuf);
// Trim newline '\r\n' or '\n'
if (linebuf.size() > 0) {
@@ -1236,10 +1410,10 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
// vertex
if (token[0] == 'v' && IS_SPACE((token[1]))) {
token += 2;
float x, y, z;
parseFloat3(&x, &y, &z, &token);
float x, y, z, w; // w is optional. default = 1.0
parseV(&x, &y, &z, &w, &token);
if (callback.vertex_cb) {
callback.vertex_cb(user_data, x, y, z);
callback.vertex_cb(user_data, x, y, z, w);
}
continue;
}
@@ -1258,10 +1432,10 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
// texcoord
if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) {
token += 3;
float x, y;
parseFloat2(&x, &y, &token);
float x, y, z; // y and z are optional. default = 0.0
parseFloat3(&x, &y, &z, &token);
if (callback.texcoord_cb) {
callback.texcoord_cb(user_data, x, y);
callback.texcoord_cb(user_data, x, y, z);
}
continue;
}
@@ -1271,15 +1445,25 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
token += 2;
token += strspn(token, " \t");
indices.clear();
while (!IS_NEW_LINE(token[0])) {
vertex_index vi = parseRawTriple(&token);
if (callback.index_cb) {
callback.index_cb(user_data, vi.v_idx, vi.vn_idx, vi.vt_idx);
}
index_t idx;
idx.vertex_index = vi.v_idx;
idx.normal_index = vi.vn_idx;
idx.texcoord_index = vi.vt_idx;
indices.push_back(idx);
size_t n = strspn(token, " \t\r");
token += n;
}
if (callback.index_cb && indices.size() > 0) {
callback.index_cb(user_data, &indices.at(0),
static_cast<int>(indices.size()));
}
continue;
}
@@ -1288,7 +1472,8 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
token += 7;
#ifdef _MSC_VER
sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf));
sscanf_s(token, "%s", namebuf,
static_cast<unsigned int>(_countof(namebuf)));
#else
sscanf(token, "%s", namebuf);
#endif
@@ -1300,12 +1485,12 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
// { error!! material not found }
}
if (newMaterialId != materialId) {
materialId = newMaterialId;
if (newMaterialId != material_id) {
material_id = newMaterialId;
}
if (callback.usemtl_cb) {
callback.usemtl_cb(user_data, namebuf, materialId);
callback.usemtl_cb(user_data, namebuf, material_id);
}
continue;
@@ -1313,6 +1498,7 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
// load mtl
if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) {
if (readMatFn) {
char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE];
token += 7;
#ifdef _MSC_VER
@@ -1322,7 +1508,7 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
#endif
std::string err_mtl;
std::vector<material_t> materials;
materials.clear();
bool ok = (*readMatFn)(namebuf, &materials, &material_map, &err_mtl);
if (err) {
(*err) += err_mtl;
@@ -1336,14 +1522,14 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
callback.mtllib_cb(user_data, &materials.at(0),
static_cast<int>(materials.size()));
}
}
continue;
}
// group name
if (token[0] == 'g' && IS_SPACE((token[1]))) {
std::vector<std::string> names;
names.reserve(2);
names.clear();
while (!IS_NEW_LINE(token[0])) {
std::string str = parseString(&token);
@@ -1353,28 +1539,26 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
assert(names.size() > 0);
std::string name;
// names[0] must be 'g', so skip the 0th element.
if (names.size() > 1) {
name = names[1];
} else {
name = "";
name.clear();
}
if (callback.group_cb) {
if (names.size() > 1) {
// create const char* array.
std::vector<const char *> tmp(names.size() - 1);
for (size_t j = 0; j < tmp.size(); j++) {
tmp[j] = names[j + 1].c_str();
names_out.resize(names.size() - 1);
for (size_t j = 0; j < names_out.size(); j++) {
names_out[j] = names[j + 1].c_str();
}
callback.group_cb(user_data, &tmp.at(0),
static_cast<int>(tmp.size()));
callback.group_cb(user_data, &names_out.at(0),
static_cast<int>(names_out.size()));
} else {
callback.group_cb(user_data, NULL, 0);
}
}
continue;
@@ -1390,10 +1574,10 @@ bool LoadObjWithCallback(void *user_data, const callback_t &callback,
#else
sscanf(token, "%s", namebuf);
#endif
std::string name = std::string(namebuf);
std::string object_name = std::string(namebuf);
if (callback.object_cb) {
callback.object_cb(user_data, name.c_str());
callback.object_cb(user_data, object_name.c_str());
}
continue;