diff --git a/.gitignore b/.gitignore index 89942d9e..02e6993b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,7 +23,8 @@ build .LSOverride # Icon must end with two \r -Icon +Icon + # Thumbnails ._* @@ -189,7 +190,6 @@ install_manifest.txt *.slo *.lo *.o -*.obj # Precompiled Headers *.gch diff --git a/CMakeLists.txt b/CMakeLists.txt index c473e2c0..e62696cc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -73,6 +73,8 @@ set(headers src/sceneStructs.h src/preview.h src/utilities.h + src/ThirdPartyLib/tiny_obj_loader.h + src/bvhTree.h src/ImGui/imconfig.h src/ImGui/imgui.h @@ -95,6 +97,8 @@ set(sources src/scene.cpp src/preview.cpp src/utilities.cpp + src/ThirdPartyLib/tiny_obj_loader.cc + src/bvhTree.cu src/ImGui/imgui.cpp src/ImGui/imgui_demo.cpp diff --git a/README.md b/README.md index 110697ce..241e4799 100644 --- a/README.md +++ b/README.md @@ -3,11 +3,44 @@ CUDA Path Tracer **University of Pennsylvania, CIS 565: GPU Programming and Architecture, Project 3** -* (TODO) YOUR NAME HERE -* Tested on: (TODO) Windows 22, i7-2222 @ 2.22GHz 22GB, GTX 222 222MB (Moore 2222 Lab) +* Zhuohao Lin + * [LinkedIn](https://www.linkedin.com/in/zhuohao-lin-960b54194/) +* Tested on: Windows 10, i7-10875H @ 2.30GHz 16GB, NVIDIA Grforce RTX 2060 6GB (personal machine) -### (TODO: Your README) +# Overview +This is a GPU based path tracer. The project contains both visual and performance improvements so that mutiple objects with different kinds of materials can be rendered quickly. +![](img/cornell.2022-10-08_06-59-29z.5000samp.png) -*DO NOT* leave the README to the last minute! It is a crucial part of the -project, and we will not be able to grade you without a good README. +# Features +### `Arbitrary mesh loading and rendering with obj file` +![](img/cornell.2022-10-08_20-44-19z.5000samp.png) +### `Antialiasing` +Before Antialiasing | After Antialiasing +:-------------------------:|:-------------------------: +![](img/beforeAntialiasing.png) | ![](img/afterAntialiasing.png) + +### `Refraction material` +Index of refraction of cube and sphere: 1.5 +![](img/cornell.2022-10-08_22-12-30z.5000samp.png) + +### `Bounding volume hierarchy acceleration data structure` +I implemented bounding volume hierarchy data structure in order to accelerate the intersection testing process. This data structure organize primitives into a binary tree based on their bounding box. There are various ways to split primitives. What I did is simply divide primitives into equal subsets. The BVH tree was built in CPU and then passed into GPU in a linear array. When doing intersection tests, first find box intersections within BVH tree apparently improve performance since a lot of primitives are abandoned. + +# Performance Analysis + +## Cache the First Bounce +For the first intersection of every iteration, it's always the same without any doubts. Therefore, I cache the first intersection data so that the computation can be skipped in every iteration except the first one. To see how much performance this brings, I make a graph as below (all data are got from the same scene) + +![](img/cacheFirstBounce.PNG) + +By caching the first bounce in the scene, we always get a higher FPS than no cache. However, as the max ray depth increases, the percentage increase in FPS is going down. + + +## Bounding Volume Hierarchy +I put 3 objects with different number of faces in the same scene for performance comparison. In order to keep the number of intersections as close as possible, I set 3 objects in similar sizes. + +![](img/bvhPerformance.PNG) + +It's obvious that BVH tree brings a huge performance improvement. The more the number of faces a mesh has, the lower the FPS will be. However, the FPS is dropping much slower using BVH than not using BVH. This is expected since BVH is a binary tree. The time cost to search intersections using BVH is roughly log(N) but N for not using BVH.
+Note: Search intersections in BVH tree is roughly log(N) because it can get into different leaf nodes if some bounding boxes overlap. diff --git a/img/afterAntialiasing.png b/img/afterAntialiasing.png new file mode 100644 index 00000000..0e216e3f Binary files /dev/null and b/img/afterAntialiasing.png differ diff --git a/img/beforeAntialiasing.png b/img/beforeAntialiasing.png new file mode 100644 index 00000000..e60ead93 Binary files /dev/null and b/img/beforeAntialiasing.png differ diff --git a/img/bvhPerformance.PNG b/img/bvhPerformance.PNG new file mode 100644 index 00000000..86eb3d79 Binary files /dev/null and b/img/bvhPerformance.PNG differ diff --git a/img/cacheFirstBounce.PNG b/img/cacheFirstBounce.PNG new file mode 100644 index 00000000..1ded52a8 Binary files /dev/null and b/img/cacheFirstBounce.PNG differ diff --git a/img/cornell.2022-09-20_19-29-53z.6samp.png b/img/cornell.2022-09-20_19-29-53z.6samp.png new file mode 100644 index 00000000..1070f907 Binary files /dev/null and b/img/cornell.2022-09-20_19-29-53z.6samp.png differ diff --git a/img/cornell.2022-09-23_04-50-13z.5000samp.png b/img/cornell.2022-09-23_04-50-13z.5000samp.png new file mode 100644 index 00000000..744a3b49 Binary files /dev/null and b/img/cornell.2022-09-23_04-50-13z.5000samp.png differ diff --git a/img/cornell.2022-09-23_04-50-41z.5000samp.png b/img/cornell.2022-09-23_04-50-41z.5000samp.png new file mode 100644 index 00000000..744a3b49 Binary files /dev/null and b/img/cornell.2022-09-23_04-50-41z.5000samp.png differ diff --git a/img/cornell.2022-09-23_05-29-46z.5000samp.png b/img/cornell.2022-09-23_05-29-46z.5000samp.png new file mode 100644 index 00000000..13e05393 Binary files /dev/null and b/img/cornell.2022-09-23_05-29-46z.5000samp.png differ diff --git a/img/cornell.2022-09-24_01-28-07z.5000samp.png b/img/cornell.2022-09-24_01-28-07z.5000samp.png new file mode 100644 index 00000000..0eb106cc Binary files /dev/null and b/img/cornell.2022-09-24_01-28-07z.5000samp.png differ diff --git a/img/cornell.2022-09-24_01-44-39z.160samp.png b/img/cornell.2022-09-24_01-44-39z.160samp.png new file mode 100644 index 00000000..58eb671b Binary files /dev/null and b/img/cornell.2022-09-24_01-44-39z.160samp.png differ diff --git a/img/cornell.2022-09-24_03-18-33z.149samp.png b/img/cornell.2022-09-24_03-18-33z.149samp.png new file mode 100644 index 00000000..2c6f2f9b Binary files /dev/null and b/img/cornell.2022-09-24_03-18-33z.149samp.png differ diff --git a/img/cornell.2022-09-28_23-33-58z.1120samp.png b/img/cornell.2022-09-28_23-33-58z.1120samp.png new file mode 100644 index 00000000..6cafc88c Binary files /dev/null and b/img/cornell.2022-09-28_23-33-58z.1120samp.png differ diff --git a/img/cornell.2022-09-28_23-33-58z.1141samp.png b/img/cornell.2022-09-28_23-33-58z.1141samp.png new file mode 100644 index 00000000..37c7124f Binary files /dev/null and b/img/cornell.2022-09-28_23-33-58z.1141samp.png differ diff --git a/img/cornell.2022-09-29_06-25-07z.123samp.png b/img/cornell.2022-09-29_06-25-07z.123samp.png new file mode 100644 index 00000000..78321442 Binary files /dev/null and b/img/cornell.2022-09-29_06-25-07z.123samp.png differ diff --git a/img/cornell.2022-09-29_06-25-07z.124samp.png b/img/cornell.2022-09-29_06-25-07z.124samp.png new file mode 100644 index 00000000..a0f056d5 Binary files /dev/null and b/img/cornell.2022-09-29_06-25-07z.124samp.png differ diff --git a/img/cornell.2022-10-03_00-41-07z.1022samp.png b/img/cornell.2022-10-03_00-41-07z.1022samp.png new file mode 100644 index 00000000..e85324ee Binary files /dev/null and b/img/cornell.2022-10-03_00-41-07z.1022samp.png differ diff --git a/img/cornell.2022-10-03_00-41-07z.728samp.png b/img/cornell.2022-10-03_00-41-07z.728samp.png new file mode 100644 index 00000000..a68d26b6 Binary files /dev/null and b/img/cornell.2022-10-03_00-41-07z.728samp.png differ diff --git a/img/cornell.2022-10-03_04-42-09z.3108samp.png b/img/cornell.2022-10-03_04-42-09z.3108samp.png new file mode 100644 index 00000000..d61b54e8 Binary files /dev/null and b/img/cornell.2022-10-03_04-42-09z.3108samp.png differ diff --git a/img/cornell.2022-10-03_04-42-09z.5000samp.png b/img/cornell.2022-10-03_04-42-09z.5000samp.png new file mode 100644 index 00000000..ca63fc00 Binary files /dev/null and b/img/cornell.2022-10-03_04-42-09z.5000samp.png differ diff --git a/img/cornell.2022-10-03_05-51-26z.1203samp.png b/img/cornell.2022-10-03_05-51-26z.1203samp.png new file mode 100644 index 00000000..b7f3313c Binary files /dev/null and b/img/cornell.2022-10-03_05-51-26z.1203samp.png differ diff --git a/img/cornell.2022-10-03_05-51-26z.2518samp.png b/img/cornell.2022-10-03_05-51-26z.2518samp.png new file mode 100644 index 00000000..de43906a Binary files /dev/null and b/img/cornell.2022-10-03_05-51-26z.2518samp.png differ diff --git a/img/cornell.2022-10-03_08-10-45z.252samp.png b/img/cornell.2022-10-03_08-10-45z.252samp.png new file mode 100644 index 00000000..e41ba60e Binary files /dev/null and b/img/cornell.2022-10-03_08-10-45z.252samp.png differ diff --git a/img/cornell.2022-10-03_08-22-15z.616samp.png b/img/cornell.2022-10-03_08-22-15z.616samp.png new file mode 100644 index 00000000..06c9a328 Binary files /dev/null and b/img/cornell.2022-10-03_08-22-15z.616samp.png differ diff --git a/img/cornell.2022-10-03_08-28-21z.1112samp.png b/img/cornell.2022-10-03_08-28-21z.1112samp.png new file mode 100644 index 00000000..18fac8b9 Binary files /dev/null and b/img/cornell.2022-10-03_08-28-21z.1112samp.png differ diff --git a/img/cornell.2022-10-03_08-55-15z.613samp.png b/img/cornell.2022-10-03_08-55-15z.613samp.png new file mode 100644 index 00000000..f481d6de Binary files /dev/null and b/img/cornell.2022-10-03_08-55-15z.613samp.png differ diff --git a/img/cornell.2022-10-04_06-10-20z.409samp.png b/img/cornell.2022-10-04_06-10-20z.409samp.png new file mode 100644 index 00000000..92c20190 Binary files /dev/null and b/img/cornell.2022-10-04_06-10-20z.409samp.png differ diff --git a/img/cornell.2022-10-04_06-13-06z.698samp.png b/img/cornell.2022-10-04_06-13-06z.698samp.png new file mode 100644 index 00000000..a67f97c5 Binary files /dev/null and b/img/cornell.2022-10-04_06-13-06z.698samp.png differ diff --git a/img/cornell.2022-10-06_06-51-29z.1689samp.png b/img/cornell.2022-10-06_06-51-29z.1689samp.png new file mode 100644 index 00000000..ab6b7ead Binary files /dev/null and b/img/cornell.2022-10-06_06-51-29z.1689samp.png differ diff --git a/img/cornell.2022-10-07_00-17-27z.838samp.png b/img/cornell.2022-10-07_00-17-27z.838samp.png new file mode 100644 index 00000000..a1273a05 Binary files /dev/null and b/img/cornell.2022-10-07_00-17-27z.838samp.png differ diff --git a/img/cornell.2022-10-08_05-56-57z.87samp.png b/img/cornell.2022-10-08_05-56-57z.87samp.png new file mode 100644 index 00000000..b4f1a8df Binary files /dev/null and b/img/cornell.2022-10-08_05-56-57z.87samp.png differ diff --git a/img/cornell.2022-10-08_06-59-29z.1213samp.png b/img/cornell.2022-10-08_06-59-29z.1213samp.png new file mode 100644 index 00000000..138a8efc Binary files /dev/null and b/img/cornell.2022-10-08_06-59-29z.1213samp.png differ diff --git a/img/cornell.2022-10-08_06-59-29z.1832samp.png b/img/cornell.2022-10-08_06-59-29z.1832samp.png new file mode 100644 index 00000000..3982282a Binary files /dev/null and b/img/cornell.2022-10-08_06-59-29z.1832samp.png differ diff --git a/img/cornell.2022-10-08_06-59-29z.1898samp.png b/img/cornell.2022-10-08_06-59-29z.1898samp.png new file mode 100644 index 00000000..86ab2594 Binary files /dev/null and b/img/cornell.2022-10-08_06-59-29z.1898samp.png differ diff --git a/img/cornell.2022-10-08_06-59-29z.2596samp.png b/img/cornell.2022-10-08_06-59-29z.2596samp.png new file mode 100644 index 00000000..d42f9615 Binary files /dev/null and b/img/cornell.2022-10-08_06-59-29z.2596samp.png differ diff --git a/img/cornell.2022-10-08_06-59-29z.352samp.png b/img/cornell.2022-10-08_06-59-29z.352samp.png new file mode 100644 index 00000000..8b6c0439 Binary files /dev/null and b/img/cornell.2022-10-08_06-59-29z.352samp.png differ diff --git a/img/cornell.2022-10-08_06-59-29z.5000samp.png b/img/cornell.2022-10-08_06-59-29z.5000samp.png new file mode 100644 index 00000000..5360687d Binary files /dev/null and b/img/cornell.2022-10-08_06-59-29z.5000samp.png differ diff --git a/img/cornell.2022-10-08_06-59-29z.664samp.png b/img/cornell.2022-10-08_06-59-29z.664samp.png new file mode 100644 index 00000000..e0051f19 Binary files /dev/null and b/img/cornell.2022-10-08_06-59-29z.664samp.png differ diff --git a/img/cornell.2022-10-08_20-44-19z.5000samp.png b/img/cornell.2022-10-08_20-44-19z.5000samp.png new file mode 100644 index 00000000..87c61618 Binary files /dev/null and b/img/cornell.2022-10-08_20-44-19z.5000samp.png differ diff --git a/img/cornell.2022-10-08_22-12-30z.5000samp.png b/img/cornell.2022-10-08_22-12-30z.5000samp.png new file mode 100644 index 00000000..c0aa1dd7 Binary files /dev/null and b/img/cornell.2022-10-08_22-12-30z.5000samp.png differ diff --git a/scenes/antialiasing.txt b/scenes/antialiasing.txt new file mode 100644 index 00000000..6c5fa416 --- /dev/null +++ b/scenes/antialiasing.txt @@ -0,0 +1,147 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0.992 0.518 0.122 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// diffuse white +MATERIAL 5 +RGB 0.98 .98 .98 +SPECEX 0 +SPECRGB .91 .89 .867 +REFL 0 +REFR 0 +REFRIOR 1.5 +EMITTANCE 0 + +// Specular white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Refractive white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 0 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Camera +CAMERA +RES 1080 1080 +FOVY 45 +ITERATIONS 500 +DEPTH 8 +FILE cornell +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Sphere +OBJECT 6 +../scenes/objFiles/cube.obj +material 5 +TRANS 0 4 0 +ROTAT 0 30 0 +SCALE 4 4 4 \ No newline at end of file diff --git a/scenes/cornell.txt b/scenes/cornell.txt index 83ff8202..d85fb763 100644 --- a/scenes/cornell.txt +++ b/scenes/cornell.txt @@ -6,7 +6,7 @@ SPECRGB 0 0 0 REFL 0 REFR 0 REFRIOR 0 -EMITTANCE 5 +EMITTANCE 10 // Diffuse white MATERIAL 1 @@ -48,6 +48,26 @@ REFR 0 REFRIOR 0 EMITTANCE 0 +// Specular white +MATERIAL 5 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Refractive yellow +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 0 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + // Camera CAMERA RES 800 800 @@ -110,8 +130,58 @@ SCALE .01 10 10 // Sphere OBJECT 6 -sphere -material 4 -TRANS -1 4 -1 +../scenes/objFiles/wahoo.obj +material 6 +TRANS 2 4 0 +ROTAT 0 0 0 +SCALE 0.35 0.35 0.35 + + +OBJECT 7 +../scenes/objFiles/wahoo.obj +material 5 +TRANS 0 4 2 +ROTAT 0 0 0 +SCALE 0.35 0.35 0.35 + +OBJECT 8 +../scenes/objFiles/wahoo.obj +material 5 +TRANS 2 1 0 +ROTAT 0 0 0 +SCALE 0.35 0.35 0.35 + +OBJECT 9 +../scenes/objFiles/wahoo.obj +material 2 +TRANS 0 1 2 +ROTAT 0 0 0 +SCALE 0.35 0.35 0.35 + +OBJECT 10 +../scenes/objFiles/wahoo.obj +material 5 +TRANS 3 1 2 +ROTAT 0 0 0 +SCALE 0.35 0.35 0.35 + +OBJECT 11 +../scenes/objFiles/wahoo.obj +material 5 +TRANS -2 4 -2 +ROTAT 0 0 0 +SCALE 0.35 0.35 0.35 + +OBJECT 12 +../scenes/objFiles/Caitlyn.obj +material 5 +TRANS 1 1 0 +ROTAT 0 0 0 +SCALE 2 2 2 + +OBJECT 13 +../scenes/objFiles/melina.obj +material 5 +TRANS -1 1 0 ROTAT 0 0 0 -SCALE 3 3 3 +SCALE 2 2 2 \ No newline at end of file diff --git a/scenes/refraction.txt b/scenes/refraction.txt new file mode 100644 index 00000000..ad30f054 --- /dev/null +++ b/scenes/refraction.txt @@ -0,0 +1,153 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0.992 0.518 0.122 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// diffuse white +MATERIAL 5 +RGB 0.98 .98 .98 +SPECEX 0 +SPECRGB .91 .89 .867 +REFL 1 +REFR 1 +REFRIOR 1.2 +EMITTANCE 0 + +// Specular white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Refractive white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 0 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Camera +CAMERA +RES 1080 1080 +FOVY 45 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +OBJECT 6 +cube +material 5 +TRANS -2 4 0 +ROTAT 0 30 0 +SCALE 3 3 3 + +OBJECT 7 +sphere +material 6 +TRANS 2 4 0 +ROTAT 0 30 0 +SCALE 3 3 3 \ No newline at end of file diff --git a/scenes/refractiveDragon.txt b/scenes/refractiveDragon.txt new file mode 100644 index 00000000..abf5252b --- /dev/null +++ b/scenes/refractiveDragon.txt @@ -0,0 +1,147 @@ +// Emissive material (light) +MATERIAL 0 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +RGB .85 .35 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +RGB .35 .85 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// rerfractive white +MATERIAL 5 +RGB 0 .98 .98 +SPECEX 0 +SPECRGB .98 .98 0 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Specular white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Refractive white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 0 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Sphere +OBJECT 6 +../scenes/objFiles/dragon.obj +material 5 +TRANS 0 4 0 +ROTAT 0 90 0 +SCALE 7.5 7.5 7.5 \ No newline at end of file diff --git a/scenes/refractiveDragon2.txt b/scenes/refractiveDragon2.txt new file mode 100644 index 00000000..b6657c82 --- /dev/null +++ b/scenes/refractiveDragon2.txt @@ -0,0 +1,147 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0.992 0.518 0.122 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// rerfractive yellow +MATERIAL 5 +RGB 0.98 .98 .98 +SPECEX 0 +SPECRGB .91 .89 .867 +REFL 0 +REFR 0 +REFRIOR 1.5 +EMITTANCE 0 + +// Specular white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Refractive white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 0 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Camera +CAMERA +RES 1080 1080 +FOVY 45 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Sphere +OBJECT 6 +../scenes/objFiles/dragon.obj +material 5 +TRANS 0 4 0 +ROTAT 0 90 0 +SCALE 8.5 8.5 8.5 \ No newline at end of file diff --git a/scenes/room.txt b/scenes/room.txt new file mode 100644 index 00000000..89e5ce85 --- /dev/null +++ b/scenes/room.txt @@ -0,0 +1,318 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0.992 0.933 0.863 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse orange (couch color) +MATERIAL 1 +RGB 1 .533 .294 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse light purple (wall color) +MATERIAL 2 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse black (tv color) +MATERIAL 3 +RGB .1 .1 .1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white (mirror color) +MATERIAL 4 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// diffuse white (floor color) +MATERIAL 5 +RGB .98 .98 0.98 +SPECEX 0 +SPECRGB .98 .98 0.98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// refraction pink (bunny) +MATERIAL 6 +RGB 1 .412 .706 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// diffuse wood color +MATERIAL 7 +RGB .73 .55 .39 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// refraction white (vase) +MATERIAL 8 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 1 +REFRIOR 1.03 +EMITTANCE 0 + +// diffuse coffee (table) +MATERIAL 9 +RGB .447 .286 .208 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// diffuse Platinum (chair) +MATERIAL 10 +RGB .91 .89 .867 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// diffuse Platinum (character) +MATERIAL 11 +RGB .91 .89 .867 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Emissive material (light on the right) +MATERIAL 12 +RGB 0.882 0.302 0.165 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Emissive material (light on the left) +MATERIAL 13 +RGB 0.992 0.518 0.122 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + + + +// Camera +CAMERA +RES 1980 1080 +FOVY 45 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light 1 +OBJECT 0 +cube +material 0 +TRANS -4 12 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Floor +OBJECT 1 +cube +material 5 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 20 .01 10 + +// Ceiling +OBJECT 2 +cube +material 5 +TRANS 0 12 0 +ROTAT 0 0 90 +SCALE .01 20 10 + +// Back wall +OBJECT 3 +cube +material 2 +TRANS 0 6 -5 +ROTAT 0 90 0 +SCALE .01 12 20 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -10 6 0 +ROTAT 0 0 0 +SCALE .01 12 10 + +// Right wall +OBJECT 5 +cube +material 2 +TRANS 10 6 0 +ROTAT 0 0 0 +SCALE .01 12 10 + +OBJECT 6 +../scenes/objFiles/room/cabinet2.obj +material 7 +TRANS 0 -0.2 -5 +ROTAT 0 0 0 +SCALE 7 7 7 + +OBJECT 7 +../scenes/objFiles/room/table.obj +material 9 +TRANS 6 0 2 +ROTAT -90 0 0 +SCALE 0.05 0.05 0.05 + +OBJECT 8 +../scenes/objFiles/room/chair.obj +material 10 +TRANS 6 0 1 +ROTAT -90 0 0 +SCALE 0.035 0.035 0.035 + +OBJECT 9 +../scenes/objFiles/room/tv.obj +material 3 +TRANS -3 3.25 -4.5 +ROTAT 90 -90 90 +SCALE 0.1 0.1 0.1 + +OBJECT 10 +../scenes/objFiles/room/couchPillows.obj +material 1 +TRANS -4 0 3.5 +ROTAT 0 180 0 +SCALE 3 2.5 2.5 + +OBJECT 11 +../scenes/objFiles/room/stairs.obj +material 7 +TRANS -9.5 0 -2 +ROTAT 0 90 0 +SCALE 0.075 0.1 0.075 + +OBJECT 12 +../scenes/objFiles/room/vaseCeramic.obj +material 8 +TRANS 8 2.01 2 +ROTAT 0 0 0 +SCALE 0.025 0.03 0.025 + +OBJECT 13 +../scenes/objFiles/room/vaseCeramic.obj +material 8 +TRANS 7.8 2.01 1.8 +ROTAT 0 0 0 +SCALE 0.025 0.03 0.025 + +OBJECT 14 +../scenes/objFiles/melina.obj +material 11 +TRANS 3 0 -1 +ROTAT 0 0 0 +SCALE 4 4 4 + +OBJECT 15 +../scenes/objFiles/bunny.obj +material 6 +TRANS 5 2.41 2 +ROTAT 0 0 0 +SCALE 0.5 0.5 0.5 + +OBJECT 16 +../scenes/objFiles/cube.obj +material 4 +TRANS 10 6 0 +ROTAT 0 90 0 +SCALE 4 3 0.01 + +// Ceiling light 2 +OBJECT 17 +cube +material 0 +TRANS 4 12 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Wall light 1 +OBJECT 18 +cube +material 12 +TRANS 10 10 -1 +ROTAT 0 90 0 +SCALE 3 0.5 .3 + +// Wall light 2 +OBJECT 19 +cube +material 12 +TRANS 10 10 3 +ROTAT 0 90 0 +SCALE 3 0.5 .3 + +// Wall light 3 +OBJECT 20 +cube +material 13 +TRANS -10 10 -1 +ROTAT 0 -90 0 +SCALE 3 0.5 .3 + +// Wall light 4 +OBJECT 21 +cube +material 13 +TRANS -10 10 3 +ROTAT 0 -90 0 +SCALE 3 0.5 .3 \ No newline at end of file diff --git a/scenes/test.txt b/scenes/test.txt new file mode 100644 index 00000000..f60c82f2 --- /dev/null +++ b/scenes/test.txt @@ -0,0 +1,146 @@ +// Emissive material (light) +MATERIAL 0 +RGB 0.992 0.518 0.122 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 5 + +// Diffuse white +MATERIAL 1 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 2 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse green +MATERIAL 3 +RGB .8 .8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 4 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// diffuse white +MATERIAL 5 +RGB 0.98 .98 .98 +SPECEX 0 +SPECRGB .91 .89 .867 +REFL 1 +REFR 1 +REFRIOR 1.2 +EMITTANCE 0 + +// Specular white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Refractive white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 0 +REFL 1 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Camera +CAMERA +RES 800 800 +FOVY 45 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +EYE 0.0 5 10.5 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 0 +TRANS 0 10 0 +ROTAT 0 0 0 +SCALE 3 .3 3 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 1 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +OBJECT 6 +../scenes/objFiles/wahoo.obj +material 1 +TRANS 0 4 0 +ROTAT 0 0 0 +SCALE 1 1 1 diff --git a/src/ThirdPartyLib/tiny_obj_loader.cc b/src/ThirdPartyLib/tiny_obj_loader.cc new file mode 100644 index 00000000..e57d0444 --- /dev/null +++ b/src/ThirdPartyLib/tiny_obj_loader.cc @@ -0,0 +1,2 @@ +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" diff --git a/src/ThirdPartyLib/tiny_obj_loader.h b/src/ThirdPartyLib/tiny_obj_loader.h new file mode 100644 index 00000000..7d0c3844 --- /dev/null +++ b/src/ThirdPartyLib/tiny_obj_loader.h @@ -0,0 +1,3333 @@ +/* +The MIT License (MIT) + +Copyright (c) 2012-Present, Syoyo Fujita and many contributors. + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +*/ + +// +// version 2.0.0 : Add new object oriented API. 1.x API is still provided. +// * Support line primitive. +// * Support points primitive. +// * Support multiple search path for .mtl(v1 API). +// * Support vertex weight `vw`(as an tinyobj extension) +// * Support escaped whitespece in mtllib +// * Add robust triangulation using Mapbox earcut(TINYOBJLOADER_USE_MAPBOX_EARCUT). +// version 1.4.0 : Modifed ParseTextureNameAndOption API +// version 1.3.1 : Make ParseTextureNameAndOption API public +// version 1.3.0 : Separate warning and error message(breaking API of LoadObj) +// version 1.2.3 : Added color space extension('-colorspace') to tex opts. +// version 1.2.2 : Parse multiple group names. +// version 1.2.1 : Added initial support for line('l') primitive(PR #178) +// version 1.2.0 : Hardened implementation(#175) +// version 1.1.1 : Support smoothing groups(#162) +// version 1.1.0 : Support parsing vertex color(#144) +// version 1.0.8 : Fix parsing `g` tag just after `usemtl`(#138) +// version 1.0.7 : Support multiple tex options(#126) +// version 1.0.6 : Add TINYOBJLOADER_USE_DOUBLE option(#124) +// version 1.0.5 : Ignore `Tr` when `d` exists in MTL(#43) +// version 1.0.4 : Support multiple filenames for 'mtllib'(#112) +// version 1.0.3 : Support parsing texture options(#85) +// version 1.0.2 : Improve parsing speed by about a factor of 2 for large +// files(#105) +// version 1.0.1 : Fixes a shape is lost if obj ends with a 'usemtl'(#104) +// version 1.0.0 : Change data structure. Change license from BSD to MIT. +// + +// +// Use this in *one* .cc +// #define TINYOBJLOADER_IMPLEMENTATION +// #include "tiny_obj_loader.h" +// + +#ifndef TINY_OBJ_LOADER_H_ +#define TINY_OBJ_LOADER_H_ + +#include +#include +#include + +namespace tinyobj { + +// TODO(syoyo): Better C++11 detection for older compiler +#if __cplusplus > 199711L +#define TINYOBJ_OVERRIDE override +#else +#define TINYOBJ_OVERRIDE +#endif + +#ifdef __clang__ +#pragma clang diagnostic push +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif + +#pragma clang diagnostic ignored "-Wpadded" + +#endif + +// https://en.wikipedia.org/wiki/Wavefront_.obj_file says ... +// +// -blendu on | off # set horizontal texture blending +// (default on) +// -blendv on | off # set vertical texture blending +// (default on) +// -boost real_value # boost mip-map sharpness +// -mm base_value gain_value # modify texture map values (default +// 0 1) +// # base_value = brightness, +// gain_value = contrast +// -o u [v [w]] # Origin offset (default +// 0 0 0) +// -s u [v [w]] # Scale (default +// 1 1 1) +// -t u [v [w]] # Turbulence (default +// 0 0 0) +// -texres resolution # texture resolution to create +// -clamp on | off # only render texels in the clamped +// 0-1 range (default off) +// # When unclamped, textures are +// repeated across a surface, +// # when clamped, only texels which +// fall within the 0-1 +// # range are rendered. +// -bm mult_value # bump multiplier (for bump maps +// only) +// +// -imfchan r | g | b | m | l | z # specifies which channel of the file +// is used to +// # create a scalar or bump texture. +// r:red, g:green, +// # b:blue, m:matte, l:luminance, +// z:z-depth.. +// # (the default for bump is 'l' and +// for decal is 'm') +// bump -imfchan r bumpmap.tga # says to use the red channel of +// bumpmap.tga as the bumpmap +// +// For reflection maps... +// +// -type sphere # specifies a sphere for a "refl" +// reflection map +// -type cube_top | cube_bottom | # when using a cube map, the texture +// file for each +// cube_front | cube_back | # side of the cube is specified +// separately +// cube_left | cube_right +// +// TinyObjLoader extension. +// +// -colorspace SPACE # Color space of the texture. e.g. +// 'sRGB` or 'linear' +// + +#ifdef TINYOBJLOADER_USE_DOUBLE +//#pragma message "using double" +typedef double real_t; +#else +//#pragma message "using float" +typedef float real_t; +#endif + +typedef enum { + TEXTURE_TYPE_NONE, // default + TEXTURE_TYPE_SPHERE, + TEXTURE_TYPE_CUBE_TOP, + TEXTURE_TYPE_CUBE_BOTTOM, + TEXTURE_TYPE_CUBE_FRONT, + TEXTURE_TYPE_CUBE_BACK, + TEXTURE_TYPE_CUBE_LEFT, + TEXTURE_TYPE_CUBE_RIGHT +} texture_type_t; + +struct texture_option_t { + texture_type_t type; // -type (default TEXTURE_TYPE_NONE) + real_t sharpness; // -boost (default 1.0?) + real_t brightness; // base_value in -mm option (default 0) + real_t contrast; // gain_value in -mm option (default 1) + real_t origin_offset[3]; // -o u [v [w]] (default 0 0 0) + real_t scale[3]; // -s u [v [w]] (default 1 1 1) + real_t turbulence[3]; // -t u [v [w]] (default 0 0 0) + int texture_resolution; // -texres resolution (No default value in the spec. + // We'll use -1) + bool clamp; // -clamp (default false) + char imfchan; // -imfchan (the default for bump is 'l' and for decal is 'm') + bool blendu; // -blendu (default on) + bool blendv; // -blendv (default on) + real_t bump_multiplier; // -bm (for bump maps only, default 1.0) + + // extension + std::string colorspace; // Explicitly specify color space of stored texel + // value. Usually `sRGB` or `linear` (default empty). +}; + +struct material_t { + std::string name; + + real_t ambient[3]; + real_t diffuse[3]; + real_t specular[3]; + real_t transmittance[3]; + real_t emission[3]; + real_t shininess; + real_t ior; // index of refraction + real_t dissolve; // 1 == opaque; 0 == fully transparent + // illumination model (see http://www.fileformat.info/format/material/) + int illum; + + int dummy; // Suppress padding warning. + + std::string ambient_texname; // map_Ka + std::string diffuse_texname; // map_Kd + std::string specular_texname; // map_Ks + std::string specular_highlight_texname; // map_Ns + std::string bump_texname; // map_bump, map_Bump, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + std::string reflection_texname; // refl + + texture_option_t ambient_texopt; + texture_option_t diffuse_texopt; + texture_option_t specular_texopt; + texture_option_t specular_highlight_texopt; + texture_option_t bump_texopt; + texture_option_t displacement_texopt; + texture_option_t alpha_texopt; + texture_option_t reflection_texopt; + + // PBR extension + // http://exocortex.com/blog/extending_wavefront_mtl_to_support_pbr + real_t roughness; // [0, 1] default 0 + real_t metallic; // [0, 1] default 0 + real_t sheen; // [0, 1] default 0 + real_t clearcoat_thickness; // [0, 1] default 0 + real_t clearcoat_roughness; // [0, 1] default 0 + real_t anisotropy; // aniso. [0, 1] default 0 + real_t anisotropy_rotation; // anisor. [0, 1] default 0 + real_t pad0; + 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. + + texture_option_t roughness_texopt; + texture_option_t metallic_texopt; + texture_option_t sheen_texopt; + texture_option_t emissive_texopt; + texture_option_t normal_texopt; + + int pad2; + + std::map unknown_parameter; + +#ifdef TINY_OBJ_LOADER_PYTHON_BINDING + // For pybind11 + std::array GetDiffuse() { + std::array values; + values[0] = double(diffuse[0]); + values[1] = double(diffuse[1]); + values[2] = double(diffuse[2]); + + return values; + } + + std::array GetSpecular() { + std::array values; + values[0] = double(specular[0]); + values[1] = double(specular[1]); + values[2] = double(specular[2]); + + return values; + } + + std::array GetTransmittance() { + std::array values; + values[0] = double(transmittance[0]); + values[1] = double(transmittance[1]); + values[2] = double(transmittance[2]); + + return values; + } + + std::array GetEmission() { + std::array values; + values[0] = double(emission[0]); + values[1] = double(emission[1]); + values[2] = double(emission[2]); + + return values; + } + + std::array GetAmbient() { + std::array values; + values[0] = double(ambient[0]); + values[1] = double(ambient[1]); + values[2] = double(ambient[2]); + + return values; + } + + void SetDiffuse(std::array &a) { + diffuse[0] = real_t(a[0]); + diffuse[1] = real_t(a[1]); + diffuse[2] = real_t(a[2]); + } + + void SetAmbient(std::array &a) { + ambient[0] = real_t(a[0]); + ambient[1] = real_t(a[1]); + ambient[2] = real_t(a[2]); + } + + void SetSpecular(std::array &a) { + specular[0] = real_t(a[0]); + specular[1] = real_t(a[1]); + specular[2] = real_t(a[2]); + } + + void SetTransmittance(std::array &a) { + transmittance[0] = real_t(a[0]); + transmittance[1] = real_t(a[1]); + transmittance[2] = real_t(a[2]); + } + + std::string GetCustomParameter(const std::string &key) { + std::map::const_iterator it = + unknown_parameter.find(key); + + if (it != unknown_parameter.end()) { + return it->second; + } + return std::string(); + } + +#endif +}; + +struct tag_t { + std::string name; + + std::vector intValues; + std::vector floatValues; + std::vector stringValues; +}; + +struct joint_and_weight_t { + int joint_id; + real_t weight; +}; + +struct skin_weight_t { + int vertex_id; // Corresponding vertex index in `attrib_t::vertices`. + // Compared to `index_t`, this index must be positive and + // start with 0(does not allow relative indexing) + std::vector weightValues; +}; + +// Index struct to support different indices for vtx/normal/texcoord. +// -1 means not used. +struct index_t { + int vertex_index; + int normal_index; + int texcoord_index; +}; + +struct mesh_t { + std::vector indices; + std::vector + num_face_vertices; // The number of vertices per + // face. 3 = triangle, 4 = quad, + // ... Up to 255 vertices per face. + std::vector material_ids; // per-face material ID + std::vector smoothing_group_ids; // per-face smoothing group + // ID(0 = off. positive value + // = group id) + std::vector tags; // SubD tag +}; + +// struct path_t { +// std::vector indices; // pairs of indices for lines +//}; + +struct lines_t { + // Linear flattened indices. + std::vector indices; // indices for vertices(poly lines) + std::vector num_line_vertices; // The number of vertices per line. +}; + +struct points_t { + std::vector indices; // indices for points +}; + +struct shape_t { + std::string name; + mesh_t mesh; + lines_t lines; + points_t points; +}; + +// Vertex attributes +struct attrib_t { + std::vector vertices; // 'v'(xyz) + + // For backward compatibility, we store vertex weight in separate array. + std::vector vertex_weights; // 'v'(w) + std::vector normals; // 'vn' + std::vector texcoords; // 'vt'(uv) + + // For backward compatibility, we store texture coordinate 'w' in separate + // array. + std::vector texcoord_ws; // 'vt'(w) + std::vector colors; // extension: vertex colors + + // + // TinyObj extension. + // + + // NOTE(syoyo): array index is based on the appearance order. + // To get a corresponding skin weight for a specific vertex id `vid`, + // Need to reconstruct a look up table: `skin_weight_t::vertex_id` == `vid` + // (e.g. using std::map, std::unordered_map) + std::vector skin_weights; + + attrib_t() {} + + // + // For pybind11 + // + const std::vector &GetVertices() const { return vertices; } + + const std::vector &GetVertexWeights() const { return vertex_weights; } +}; + +struct callback_t { + // W is optional and set to 1 if there is no `w` item in `v` line + void (*vertex_cb)(void *user_data, real_t x, real_t y, real_t z, real_t w); + void (*normal_cb)(void *user_data, real_t x, real_t y, real_t z); + + // 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, real_t x, real_t y, real_t 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 material_id); + // `materials` = parsed material data. + void (*mtllib_cb)(void *user_data, const material_t *materials, + int num_materials); + // There may be multiple group names + void (*group_cb)(void *user_data, const char **names, int num_names); + void (*object_cb)(void *user_data, const char *name); + + callback_t() + : vertex_cb(NULL), + normal_cb(NULL), + texcoord_cb(NULL), + index_cb(NULL), + usemtl_cb(NULL), + mtllib_cb(NULL), + group_cb(NULL), + object_cb(NULL) {} +}; + +class MaterialReader { + public: + MaterialReader() {} + virtual ~MaterialReader(); + + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) = 0; +}; + +/// +/// Read .mtl from a file. +/// +class MaterialFileReader : public MaterialReader { + public: + // Path could contain separator(';' in Windows, ':' in Posix) + explicit MaterialFileReader(const std::string &mtl_basedir) + : m_mtlBaseDir(mtl_basedir) {} + virtual ~MaterialFileReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) TINYOBJ_OVERRIDE; + + private: + std::string m_mtlBaseDir; +}; + +/// +/// Read .mtl from a stream. +/// +class MaterialStreamReader : public MaterialReader { + public: + explicit MaterialStreamReader(std::istream &inStream) + : m_inStream(inStream) {} + virtual ~MaterialStreamReader() TINYOBJ_OVERRIDE {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *warn, + std::string *err) TINYOBJ_OVERRIDE; + + private: + std::istream &m_inStream; +}; + +// v2 API +struct ObjReaderConfig { + bool triangulate; // triangulate polygon? + + // Currently not used. + // "simple" or empty: Create triangle fan + // "earcut": Use the algorithm based on Ear clipping + std::string triangulation_method; + + /// Parse vertex color. + /// If vertex color is not present, its filled with default value. + /// false = no vertex color + /// This will increase memory of parsed .obj + bool vertex_color; + + /// + /// Search path to .mtl file. + /// Default = "" = search from the same directory of .obj file. + /// Valid only when loading .obj from a file. + /// + std::string mtl_search_path; + + ObjReaderConfig() + : triangulate(true), triangulation_method("simple"), vertex_color(true) {} +}; + +/// +/// Wavefront .obj reader class(v2 API) +/// +class ObjReader { + public: + ObjReader() : valid_(false) {} + + /// + /// Load .obj and .mtl from a file. + /// + /// @param[in] filename wavefront .obj filename + /// @param[in] config Reader configuration + /// + bool ParseFromFile(const std::string &filename, + const ObjReaderConfig &config = ObjReaderConfig()); + + /// + /// Parse .obj from a text string. + /// Need to supply .mtl text string by `mtl_text`. + /// This function ignores `mtllib` line in .obj text. + /// + /// @param[in] obj_text wavefront .obj filename + /// @param[in] mtl_text wavefront .mtl filename + /// @param[in] config Reader configuration + /// + bool ParseFromString(const std::string &obj_text, const std::string &mtl_text, + const ObjReaderConfig &config = ObjReaderConfig()); + + /// + /// .obj was loaded or parsed correctly. + /// + bool Valid() const { return valid_; } + + const attrib_t &GetAttrib() const { return attrib_; } + + const std::vector &GetShapes() const { return shapes_; } + + const std::vector &GetMaterials() const { return materials_; } + + /// + /// Warning message(may be filled after `Load` or `Parse`) + /// + const std::string &Warning() const { return warning_; } + + /// + /// Error message(filled when `Load` or `Parse` failed) + /// + const std::string &Error() const { return error_; } + + private: + bool valid_; + + attrib_t attrib_; + std::vector shapes_; + std::vector materials_; + + std::string warning_; + std::string error_; +}; + +/// ==>>========= Legacy v1 API ============================================= + +/// Loads .obj from a file. +/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data +/// 'shapes' will be filled with parsed shape data +/// Returns true when loading .obj become success. +/// Returns warning message into `warn`, and error message into `err` +/// 'mtl_basedir' is optional, and used for base directory for .mtl file. +/// In default(`NULL'), .mtl file is searched from an application's working +/// directory. +/// 'triangulate' is optional, and used whether triangulate polygon face in .obj +/// or not. +/// Option 'default_vcols_fallback' specifies whether vertex colors should +/// always be defined, even if no colors are given (fallback to white). +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, const char *filename, + const char *mtl_basedir = NULL, bool triangulate = true, + bool default_vcols_fallback = true); + +/// Loads .obj from a file with custom user callback. +/// .mtl is loaded as usual and parsed material_t data will be passed to +/// `callback.mtllib_cb`. +/// Returns true when loading .obj/.mtl become success. +/// Returns warning message into `warn`, and error message into `err` +/// 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 *warn = NULL, std::string *err = NULL); + +/// Loads object from a std::istream, uses `readMatFn` to retrieve +/// std::istream for materials. +/// Returns true when loading .obj become success. +/// Returns warning and error message into `err` +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn = NULL, bool triangulate = true, + bool default_vcols_fallback = true); + +/// Loads materials into std::map +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning, std::string *err); + +/// +/// Parse texture name and texture option for custom texture parameter through +/// material::unknown_parameter +/// +/// @param[out] texname Parsed texture name +/// @param[out] texopt Parsed texopt +/// @param[in] linebuf Input string +/// +bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, + const char *linebuf); + +/// =<<========== Legacy v1 API ============================================= + +} // namespace tinyobj + +#endif // TINY_OBJ_LOADER_H_ + +#ifdef TINYOBJLOADER_IMPLEMENTATION +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + +#ifdef TINYOBJLOADER_DONOT_INCLUDE_MAPBOX_EARCUT +// Assume earcut.hpp is included outside of tiny_obj_loader.h +#else + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Weverything" +#endif + +#include +#include "mapbox/earcut.hpp" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#endif + +#endif // TINYOBJLOADER_USE_MAPBOX_EARCUT + +namespace tinyobj { + +MaterialReader::~MaterialReader() {} + +struct vertex_index_t { + int v_idx, vt_idx, vn_idx; + vertex_index_t() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit vertex_index_t(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} + vertex_index_t(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} +}; + +// Internal data structure for face representation +// index + smoothing group. +struct face_t { + unsigned int + smoothing_group_id; // smoothing group id. 0 = smoothing groupd is off. + int pad_; + std::vector vertex_indices; // face vertex indices. + + face_t() : smoothing_group_id(0), pad_(0) {} +}; + +// Internal data structure for line representation +struct __line_t { + // l v1/vt1 v2/vt2 ... + // In the specification, line primitrive does not have normal index, but + // TinyObjLoader allow it + std::vector vertex_indices; +}; + +// Internal data structure for points representation +struct __points_t { + // p v1 v2 ... + // In the specification, point primitrive does not have normal index and + // texture coord index, but TinyObjLoader allow it. + std::vector vertex_indices; +}; + +struct tag_sizes { + tag_sizes() : num_ints(0), num_reals(0), num_strings(0) {} + int num_ints; + int num_reals; + int num_strings; +}; + +struct obj_shape { + std::vector v; + std::vector vn; + std::vector vt; +}; + +// +// Manages group of primitives(face, line, points, ...) +struct PrimGroup { + std::vector faceGroup; + std::vector<__line_t> lineGroup; + std::vector<__points_t> pointsGroup; + + void clear() { + faceGroup.clear(); + lineGroup.clear(); + pointsGroup.clear(); + } + + bool IsEmpty() const { + return faceGroup.empty() && lineGroup.empty() && pointsGroup.empty(); + } + + // TODO(syoyo): bspline, surface, ... +}; + +// 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(); + + if (se) { + 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(c); + } + } + } + + return is; +} + +#define IS_SPACE(x) (((x) == ' ') || ((x) == '\t')) +#define IS_DIGIT(x) \ + (static_cast((x) - '0') < static_cast(10)) +#define IS_NEW_LINE(x) (((x) == '\r') || ((x) == '\n') || ((x) == '\0')) + +// Make index zero-base, and also support relative index. +static inline bool fixIndex(int idx, int n, int *ret) { + if (!ret) { + return false; + } + + if (idx > 0) { + (*ret) = idx - 1; + return true; + } + + if (idx == 0) { + // zero is not allowed according to the spec. + return false; + } + + if (idx < 0) { + (*ret) = n + idx; // negative value = relative + return true; + } + + return false; // never reach here. +} + +static inline std::string parseString(const char **token) { + std::string s; + (*token) += strspn((*token), " \t"); + size_t e = strcspn((*token), " \t\r"); + s = std::string((*token), &(*token)[e]); + (*token) += e; + return s; +} + +static inline int parseInt(const char **token) { + (*token) += strspn((*token), " \t"); + int i = atoi((*token)); + (*token) += strcspn((*token), " \t\r"); + return i; +} + +// Tries to parse a floating point number located at s. +// +// s_end should be a location in the string where reading should absolutely +// stop. For example at the end of the string, to prevent buffer overflows. +// +// Parses the following EBNF grammar: +// sign = "+" | "-" ; +// END = ? anything not in digit ? +// digit = "0" | "1" | "2" | "3" | "4" | "5" | "6" | "7" | "8" | "9" ; +// integer = [sign] , digit , {digit} ; +// decimal = integer , ["." , integer] ; +// float = ( decimal , END ) | ( decimal , ("E" | "e") , integer , END ) ; +// +// Valid strings are for example: +// -0 +3.1417e+2 -0.0E-3 1.0324 -1.41 11e2 +// +// If the parsing is a success, result is set to the parsed value and true +// is returned. +// +// The function is greedy and will parse until any of the following happens: +// - a non-conforming character is encountered. +// - s_end is reached. +// +// The following situations triggers a failure: +// - s >= s_end. +// - parse failure. +// +static bool tryParseDouble(const char *s, const char *s_end, double *result) { + if (s >= s_end) { + return false; + } + + double mantissa = 0.0; + // This exponent is base 2 rather than 10. + // However the exponent we parse is supposed to be one of ten, + // thus we must take care to convert the exponent/and or the + // mantissa to a * 2^E, where a is the mantissa and E is the + // exponent. + // To get the final double we will use ldexp, it requires the + // exponent to be in base 2. + int exponent = 0; + + // NOTE: THESE MUST BE DECLARED HERE SINCE WE ARE NOT ALLOWED + // TO JUMP OVER DEFINITIONS. + char sign = '+'; + char exp_sign = '+'; + char const *curr = s; + + // How many characters were read in a loop. + int read = 0; + // Tells whether a loop terminated due to reaching s_end. + bool end_not_reached = false; + bool leading_decimal_dots = false; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + if ((curr != s_end) && (*curr == '.')) { + // accept. Somethig like `.7e+2`, `-.5234` + leading_decimal_dots = true; + } + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else if (*curr == '.') { + // accept. Somethig like `.7e+2`, `-.5234` + leading_decimal_dots = true; + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + if (!leading_decimal_dots) { + while (end_not_reached && IS_DIGIT(*curr)) { + mantissa *= 10; + mantissa += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + + // We must make sure we actually got something. + if (read == 0) goto fail; + } + + // We allow numbers of form "#", "###" etc. + if (!end_not_reached) goto assemble; + + // Read the decimal part. + if (*curr == '.') { + curr++; + read = 1; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + static const double pow_lut[] = { + 1.0, 0.1, 0.01, 0.001, 0.0001, 0.00001, 0.000001, 0.0000001, + }; + const int lut_entries = sizeof pow_lut / sizeof pow_lut[0]; + + // NOTE: Don't use powf here, it will absolutely murder precision. + mantissa += static_cast(*curr - 0x30) * + (read < lut_entries ? pow_lut[read] : std::pow(10.0, -read)); + read++; + curr++; + end_not_reached = (curr != s_end); + } + } else if (*curr == 'e' || *curr == 'E') { + } else { + goto assemble; + } + + if (!end_not_reached) goto assemble; + + // Read the exponent part. + if (*curr == 'e' || *curr == 'E') { + curr++; + // Figure out if a sign is present and if it is. + end_not_reached = (curr != s_end); + if (end_not_reached && (*curr == '+' || *curr == '-')) { + exp_sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + // Empty E is not allowed. + goto fail; + } + + read = 0; + end_not_reached = (curr != s_end); + while (end_not_reached && IS_DIGIT(*curr)) { + // To avoid annoying MSVC's min/max macro definiton, + // Use hardcoded int max value + if (exponent > (2147483647/10)) { // 2147483647 = std::numeric_limits::max() + // Integer overflow + goto fail; + } + exponent *= 10; + exponent += static_cast(*curr - 0x30); + curr++; + read++; + end_not_reached = (curr != s_end); + } + exponent *= (exp_sign == '+' ? 1 : -1); + if (read == 0) goto fail; + } + +assemble: + *result = (sign == '+' ? 1 : -1) * + (exponent ? std::ldexp(mantissa * std::pow(5.0, exponent), exponent) + : mantissa); + return true; +fail: + return false; +} + +static inline real_t parseReal(const char **token, double default_value = 0.0) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val = default_value; + tryParseDouble((*token), end, &val); + real_t f = static_cast(val); + (*token) = end; + return f; +} + +static inline bool parseReal(const char **token, real_t *out) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + double val; + bool ret = tryParseDouble((*token), end, &val); + if (ret) { + real_t f = static_cast(val); + (*out) = f; + } + (*token) = end; + return ret; +} + +static inline void parseReal2(real_t *x, real_t *y, const char **token, + const double default_x = 0.0, + const double default_y = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); +} + +static inline void parseReal3(real_t *x, real_t *y, real_t *z, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); +} + +static inline void parseV(real_t *x, real_t *y, real_t *z, real_t *w, + const char **token, const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0, + const double default_w = 1.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + (*w) = parseReal(token, default_w); +} + +// Extension: parse vertex with colors(6 items) +static inline bool parseVertexWithColor(real_t *x, real_t *y, real_t *z, + real_t *r, real_t *g, real_t *b, + const char **token, + const double default_x = 0.0, + const double default_y = 0.0, + const double default_z = 0.0) { + (*x) = parseReal(token, default_x); + (*y) = parseReal(token, default_y); + (*z) = parseReal(token, default_z); + + const bool found_color = + parseReal(token, r) && parseReal(token, g) && parseReal(token, b); + + if (!found_color) { + (*r) = (*g) = (*b) = 1.0; + } + + return found_color; +} + +static inline bool parseOnOff(const char **token, bool default_value = true) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + + bool ret = default_value; + if ((0 == strncmp((*token), "on", 2))) { + ret = true; + } else if ((0 == strncmp((*token), "off", 3))) { + ret = false; + } + + (*token) = end; + return ret; +} + +static inline texture_type_t parseTextureType( + const char **token, texture_type_t default_value = TEXTURE_TYPE_NONE) { + (*token) += strspn((*token), " \t"); + const char *end = (*token) + strcspn((*token), " \t\r"); + texture_type_t ty = default_value; + + if ((0 == strncmp((*token), "cube_top", strlen("cube_top")))) { + ty = TEXTURE_TYPE_CUBE_TOP; + } else if ((0 == strncmp((*token), "cube_bottom", strlen("cube_bottom")))) { + ty = TEXTURE_TYPE_CUBE_BOTTOM; + } else if ((0 == strncmp((*token), "cube_left", strlen("cube_left")))) { + ty = TEXTURE_TYPE_CUBE_LEFT; + } else if ((0 == strncmp((*token), "cube_right", strlen("cube_right")))) { + ty = TEXTURE_TYPE_CUBE_RIGHT; + } else if ((0 == strncmp((*token), "cube_front", strlen("cube_front")))) { + ty = TEXTURE_TYPE_CUBE_FRONT; + } else if ((0 == strncmp((*token), "cube_back", strlen("cube_back")))) { + ty = TEXTURE_TYPE_CUBE_BACK; + } else if ((0 == strncmp((*token), "sphere", strlen("sphere")))) { + ty = TEXTURE_TYPE_SPHERE; + } + + (*token) = end; + return ty; +} + +static tag_sizes parseTagTriple(const char **token) { + tag_sizes ts; + + (*token) += strspn((*token), " \t"); + ts.num_ints = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + + (*token)++; // Skip '/' + + (*token) += strspn((*token), " \t"); + ts.num_reals = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + (*token)++; // Skip '/' + + ts.num_strings = parseInt(token); + + return ts; +} + +// Parse triples with index offsets: i, i/j/k, i//k, i/j +static bool parseTriple(const char **token, int vsize, int vnsize, int vtsize, + vertex_index_t *ret) { + if (!ret) { + return false; + } + + vertex_index_t vi(-1); + + if (!fixIndex(atoi((*token)), vsize, &(vi.v_idx))) { + return false; + } + + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + (*ret) = vi; + return true; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); + (*ret) = vi; + return true; + } + + // i/j/k or i/j + if (!fixIndex(atoi((*token)), vtsize, &(vi.vt_idx))) { + return false; + } + + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + (*ret) = vi; + return true; + } + + // i/j/k + (*token)++; // skip '/' + if (!fixIndex(atoi((*token)), vnsize, &(vi.vn_idx))) { + return false; + } + (*token) += strcspn((*token), "/ \t\r"); + + (*ret) = vi; + + return true; +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index_t parseRawTriple(const char **token) { + vertex_index_t vi(static_cast(0)); // 0 is an invalid index in OBJ + + vi.v_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + return vi; +} + +bool ParseTextureNameAndOption(std::string *texname, texture_option_t *texopt, + const char *linebuf) { + // @todo { write more robust lexer and parser. } + bool found_texname = false; + std::string texture_name; + + const char *token = linebuf; // Assume line ends with NULL + + while (!IS_NEW_LINE((*token))) { + token += strspn(token, " \t"); // skip space + if ((0 == strncmp(token, "-blendu", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendu = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-blendv", 7)) && IS_SPACE((token[7]))) { + token += 8; + texopt->blendv = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-clamp", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->clamp = parseOnOff(&token, /* default */ true); + } else if ((0 == strncmp(token, "-boost", 6)) && IS_SPACE((token[6]))) { + token += 7; + texopt->sharpness = parseReal(&token, 1.0); + } else if ((0 == strncmp(token, "-bm", 3)) && IS_SPACE((token[3]))) { + token += 4; + texopt->bump_multiplier = parseReal(&token, 1.0); + } else if ((0 == strncmp(token, "-o", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->origin_offset[0]), &(texopt->origin_offset[1]), + &(texopt->origin_offset[2]), &token); + } else if ((0 == strncmp(token, "-s", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->scale[0]), &(texopt->scale[1]), &(texopt->scale[2]), + &token, 1.0, 1.0, 1.0); + } else if ((0 == strncmp(token, "-t", 2)) && IS_SPACE((token[2]))) { + token += 3; + parseReal3(&(texopt->turbulence[0]), &(texopt->turbulence[1]), + &(texopt->turbulence[2]), &token); + } else if ((0 == strncmp(token, "-type", 5)) && IS_SPACE((token[5]))) { + token += 5; + texopt->type = parseTextureType((&token), TEXTURE_TYPE_NONE); + } else if ((0 == strncmp(token, "-texres", 7)) && IS_SPACE((token[7]))) { + token += 7; + // TODO(syoyo): Check if arg is int type. + texopt->texture_resolution = parseInt(&token); + } else if ((0 == strncmp(token, "-imfchan", 8)) && IS_SPACE((token[8]))) { + token += 9; + token += strspn(token, " \t"); + const char *end = token + strcspn(token, " \t\r"); + if ((end - token) == 1) { // Assume one char for -imfchan + texopt->imfchan = (*token); + } + token = end; + } else if ((0 == strncmp(token, "-mm", 3)) && IS_SPACE((token[3]))) { + token += 4; + parseReal2(&(texopt->brightness), &(texopt->contrast), &token, 0.0, 1.0); + } else if ((0 == strncmp(token, "-colorspace", 11)) && + IS_SPACE((token[11]))) { + token += 12; + texopt->colorspace = parseString(&token); + } else { +// Assume texture filename +#if 0 + size_t len = strcspn(token, " \t\r"); // untile next space + texture_name = std::string(token, token + len); + token += len; + + token += strspn(token, " \t"); // skip space +#else + // Read filename until line end to parse filename containing whitespace + // TODO(syoyo): Support parsing texture option flag after the filename. + texture_name = std::string(token); + token += texture_name.length(); +#endif + + found_texname = true; + } + } + + if (found_texname) { + (*texname) = texture_name; + return true; + } else { + return false; + } +} + +static void InitTexOpt(texture_option_t *texopt, const bool is_bump) { + if (is_bump) { + texopt->imfchan = 'l'; + } else { + texopt->imfchan = 'm'; + } + texopt->bump_multiplier = static_cast(1.0); + texopt->clamp = false; + texopt->blendu = true; + texopt->blendv = true; + texopt->sharpness = static_cast(1.0); + texopt->brightness = static_cast(0.0); + texopt->contrast = static_cast(1.0); + texopt->origin_offset[0] = static_cast(0.0); + texopt->origin_offset[1] = static_cast(0.0); + texopt->origin_offset[2] = static_cast(0.0); + texopt->scale[0] = static_cast(1.0); + texopt->scale[1] = static_cast(1.0); + texopt->scale[2] = static_cast(1.0); + texopt->turbulence[0] = static_cast(0.0); + texopt->turbulence[1] = static_cast(0.0); + texopt->turbulence[2] = static_cast(0.0); + texopt->texture_resolution = -1; + texopt->type = TEXTURE_TYPE_NONE; +} + +static void InitMaterial(material_t *material) { + InitTexOpt(&material->ambient_texopt, /* is_bump */ false); + InitTexOpt(&material->diffuse_texopt, /* is_bump */ false); + InitTexOpt(&material->specular_texopt, /* is_bump */ false); + InitTexOpt(&material->specular_highlight_texopt, /* is_bump */ false); + InitTexOpt(&material->bump_texopt, /* is_bump */ true); + InitTexOpt(&material->displacement_texopt, /* is_bump */ false); + InitTexOpt(&material->alpha_texopt, /* is_bump */ false); + InitTexOpt(&material->reflection_texopt, /* is_bump */ false); + InitTexOpt(&material->roughness_texopt, /* is_bump */ false); + InitTexOpt(&material->metallic_texopt, /* is_bump */ false); + InitTexOpt(&material->sheen_texopt, /* is_bump */ false); + InitTexOpt(&material->emissive_texopt, /* is_bump */ false); + InitTexOpt(&material->normal_texopt, + /* is_bump */ false); // @fixme { is_bump will be true? } + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->reflection_texname = ""; + material->alpha_texname = ""; + for (int i = 0; i < 3; i++) { + material->ambient[i] = static_cast(0.0); + material->diffuse[i] = static_cast(0.0); + material->specular[i] = static_cast(0.0); + material->transmittance[i] = static_cast(0.0); + material->emission[i] = static_cast(0.0); + } + material->illum = 0; + material->dissolve = static_cast(1.0); + material->shininess = static_cast(1.0); + material->ior = static_cast(1.0); + + material->roughness = static_cast(0.0); + material->metallic = static_cast(0.0); + material->sheen = static_cast(0.0); + material->clearcoat_thickness = static_cast(0.0); + material->clearcoat_roughness = static_cast(0.0); + material->anisotropy_rotation = static_cast(0.0); + material->anisotropy = static_cast(0.0); + material->roughness_texname = ""; + material->metallic_texname = ""; + material->sheen_texname = ""; + material->emissive_texname = ""; + material->normal_texname = ""; + + material->unknown_parameter.clear(); +} + +// code from https://wrf.ecse.rpi.edu//Research/Short_Notes/pnpoly.html +template +static int pnpoly(int nvert, T *vertx, T *verty, T testx, T testy) { + int i, j, c = 0; + for (i = 0, j = nvert - 1; i < nvert; j = i++) { + if (((verty[i] > testy) != (verty[j] > testy)) && + (testx < + (vertx[j] - vertx[i]) * (testy - verty[i]) / (verty[j] - verty[i]) + + vertx[i])) + c = !c; + } + return c; +} + +// TODO(syoyo): refactor function. +static bool exportGroupsToShape(shape_t *shape, const PrimGroup &prim_group, + const std::vector &tags, + const int material_id, const std::string &name, + bool triangulate, const std::vector &v, + std::string *warn) { + if (prim_group.IsEmpty()) { + return false; + } + + shape->name = name; + + // polygon + if (!prim_group.faceGroup.empty()) { + // Flatten vertices and indices + for (size_t i = 0; i < prim_group.faceGroup.size(); i++) { + const face_t &face = prim_group.faceGroup[i]; + + size_t npolys = face.vertex_indices.size(); + + if (npolys < 3) { + // Face must have 3+ vertices. + if (warn) { + (*warn) += "Degenerated face found\n."; + } + continue; + } + + if (triangulate) { + if (npolys == 4) { + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1 = face.vertex_indices[1]; + vertex_index_t i2 = face.vertex_indices[2]; + vertex_index_t i3 = face.vertex_indices[3]; + + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + size_t vi3 = size_t(i3.v_idx); + + if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || + ((3 * vi2 + 2) >= v.size()) || ((3 * vi3 + 2) >= v.size())) { + // Invalid triangle. + // FIXME(syoyo): Is it ok to simply skip this invalid triangle? + if (warn) { + (*warn) += "Face with invalid vertex index found.\n"; + } + continue; + } + + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t v3x = v[vi3 * 3 + 0]; + real_t v3y = v[vi3 * 3 + 1]; + real_t v3z = v[vi3 * 3 + 2]; + + // There are two candidates to split the quad into two triangles. + // + // Choose the shortest edge. + // TODO: Is it better to determine the edge to split by calculating + // the area of each triangle? + // + // +---+ + // |\ | + // | \ | + // | \| + // +---+ + // + // +---+ + // | /| + // | / | + // |/ | + // +---+ + + real_t e02x = v2x - v0x; + real_t e02y = v2y - v0y; + real_t e02z = v2z - v0z; + real_t e13x = v3x - v1x; + real_t e13y = v3y - v1y; + real_t e13z = v3z - v1z; + + real_t sqr02 = e02x * e02x + e02y * e02y + e02z * e02z; + real_t sqr13 = e13x * e13x + e13y * e13y + e13z * e13z; + + index_t idx0, idx1, idx2, idx3; + + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + idx3.vertex_index = i3.v_idx; + idx3.normal_index = i3.vn_idx; + idx3.texcoord_index = i3.vt_idx; + + if (sqr02 < sqr13) { + // [0, 1, 2], [0, 2, 3] + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx2); + shape->mesh.indices.push_back(idx3); + } else { + // [0, 1, 3], [1, 2, 3] + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx3); + + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + shape->mesh.indices.push_back(idx3); + } + + // Two triangle faces + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.num_face_vertices.push_back(3); + + shape->mesh.material_ids.push_back(material_id); + shape->mesh.material_ids.push_back(material_id); + + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + shape->mesh.smoothing_group_ids.push_back(face.smoothing_group_id); + + } else { + vertex_index_t i0 = face.vertex_indices[0]; + vertex_index_t i1(-1); + vertex_index_t i2 = face.vertex_indices[1]; + + // find the two axes to work in + size_t axes[2] = {1, 2}; + for (size_t k = 0; k < npolys; ++k) { + i0 = face.vertex_indices[(k + 0) % npolys]; + i1 = face.vertex_indices[(k + 1) % npolys]; + i2 = face.vertex_indices[(k + 2) % npolys]; + size_t vi0 = size_t(i0.v_idx); + size_t vi1 = size_t(i1.v_idx); + size_t vi2 = size_t(i2.v_idx); + + if (((3 * vi0 + 2) >= v.size()) || ((3 * vi1 + 2) >= v.size()) || + ((3 * vi2 + 2) >= v.size())) { + // Invalid triangle. + // FIXME(syoyo): Is it ok to simply skip this invalid triangle? + continue; + } + real_t v0x = v[vi0 * 3 + 0]; + real_t v0y = v[vi0 * 3 + 1]; + real_t v0z = v[vi0 * 3 + 2]; + real_t v1x = v[vi1 * 3 + 0]; + real_t v1y = v[vi1 * 3 + 1]; + real_t v1z = v[vi1 * 3 + 2]; + real_t v2x = v[vi2 * 3 + 0]; + real_t v2y = v[vi2 * 3 + 1]; + real_t v2z = v[vi2 * 3 + 2]; + real_t e0x = v1x - v0x; + real_t e0y = v1y - v0y; + real_t e0z = v1z - v0z; + real_t e1x = v2x - v1x; + real_t e1y = v2y - v1y; + real_t e1z = v2z - v1z; + real_t cx = std::fabs(e0y * e1z - e0z * e1y); + real_t cy = std::fabs(e0z * e1x - e0x * e1z); + real_t cz = std::fabs(e0x * e1y - e0y * e1x); + const real_t epsilon = std::numeric_limits::epsilon(); + // std::cout << "cx " << cx << ", cy " << cy << ", cz " << cz << + // "\n"; + if (cx > epsilon || cy > epsilon || cz > epsilon) { + // std::cout << "corner\n"; + // found a corner + if (cx > cy && cx > cz) { + // std::cout << "pattern0\n"; + } else { + // std::cout << "axes[0] = 0\n"; + axes[0] = 0; + if (cz > cx && cz > cy) { + // std::cout << "axes[1] = 1\n"; + axes[1] = 1; + } + } + break; + } + } + +#ifdef TINYOBJLOADER_USE_MAPBOX_EARCUT + using Point = std::array; + + // first polyline define the main polygon. + // following polylines define holes(not used in tinyobj). + std::vector > polygon; + + std::vector polyline; + + // Fill polygon data(facevarying vertices). + for (size_t k = 0; k < npolys; k++) { + i0 = face.vertex_indices[k]; + size_t vi0 = size_t(i0.v_idx); + + assert(((3 * vi0 + 2) < v.size())); + + real_t v0x = v[vi0 * 3 + axes[0]]; + real_t v0y = v[vi0 * 3 + axes[1]]; + + polyline.push_back({v0x, v0y}); + } + + polygon.push_back(polyline); + std::vector indices = mapbox::earcut(polygon); + // => result = 3 * faces, clockwise + + assert(indices.size() % 3 == 0); + + // Reconstruct vertex_index_t + for (size_t k = 0; k < indices.size() / 3; k++) { + { + index_t idx0, idx1, idx2; + idx0.vertex_index = face.vertex_indices[indices[3 * k + 0]].v_idx; + idx0.normal_index = + face.vertex_indices[indices[3 * k + 0]].vn_idx; + idx0.texcoord_index = + face.vertex_indices[indices[3 * k + 0]].vt_idx; + idx1.vertex_index = face.vertex_indices[indices[3 * k + 1]].v_idx; + idx1.normal_index = + face.vertex_indices[indices[3 * k + 1]].vn_idx; + idx1.texcoord_index = + face.vertex_indices[indices[3 * k + 1]].vt_idx; + idx2.vertex_index = face.vertex_indices[indices[3 * k + 2]].v_idx; + idx2.normal_index = + face.vertex_indices[indices[3 * k + 2]].vn_idx; + idx2.texcoord_index = + face.vertex_indices[indices[3 * k + 2]].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + } + +#else // Built-in ear clipping triangulation + + + face_t remainingFace = face; // copy + size_t guess_vert = 0; + vertex_index_t ind[3]; + real_t vx[3]; + real_t vy[3]; + + // How many iterations can we do without decreasing the remaining + // vertices. + size_t remainingIterations = face.vertex_indices.size(); + size_t previousRemainingVertices = + remainingFace.vertex_indices.size(); + + while (remainingFace.vertex_indices.size() > 3 && + remainingIterations > 0) { + // std::cout << "remainingIterations " << remainingIterations << + // "\n"; + + npolys = remainingFace.vertex_indices.size(); + if (guess_vert >= npolys) { + guess_vert -= npolys; + } + + if (previousRemainingVertices != npolys) { + // The number of remaining vertices decreased. Reset counters. + previousRemainingVertices = npolys; + remainingIterations = npolys; + } else { + // We didn't consume a vertex on previous iteration, reduce the + // available iterations. + remainingIterations--; + } + + for (size_t k = 0; k < 3; k++) { + ind[k] = remainingFace.vertex_indices[(guess_vert + k) % npolys]; + size_t vi = size_t(ind[k].v_idx); + if (((vi * 3 + axes[0]) >= v.size()) || + ((vi * 3 + axes[1]) >= v.size())) { + // ??? + vx[k] = static_cast(0.0); + vy[k] = static_cast(0.0); + } else { + vx[k] = v[vi * 3 + axes[0]]; + vy[k] = v[vi * 3 + axes[1]]; + } + } + + // + // area is calculated per face + // + real_t e0x = vx[1] - vx[0]; + real_t e0y = vy[1] - vy[0]; + real_t e1x = vx[2] - vx[1]; + real_t e1y = vy[2] - vy[1]; + real_t cross = e0x * e1y - e0y * e1x; + // std::cout << "axes = " << axes[0] << ", " << axes[1] << "\n"; + // std::cout << "e0x, e0y, e1x, e1y " << e0x << ", " << e0y << ", " + // << e1x << ", " << e1y << "\n"; + + real_t area = (vx[0] * vy[1] - vy[0] * vx[1]) * static_cast(0.5); + // std::cout << "cross " << cross << ", area " << area << "\n"; + // if an internal angle + if (cross * area < static_cast(0.0)) { + // std::cout << "internal \n"; + guess_vert += 1; + // std::cout << "guess vert : " << guess_vert << "\n"; + continue; + } + + // check all other verts in case they are inside this triangle + bool overlap = false; + for (size_t otherVert = 3; otherVert < npolys; ++otherVert) { + size_t idx = (guess_vert + otherVert) % npolys; + + if (idx >= remainingFace.vertex_indices.size()) { + // std::cout << "???0\n"; + // ??? + continue; + } + + size_t ovi = size_t(remainingFace.vertex_indices[idx].v_idx); + + if (((ovi * 3 + axes[0]) >= v.size()) || + ((ovi * 3 + axes[1]) >= v.size())) { + // std::cout << "???1\n"; + // ??? + continue; + } + real_t tx = v[ovi * 3 + axes[0]]; + real_t ty = v[ovi * 3 + axes[1]]; + if (pnpoly(3, vx, vy, tx, ty)) { + // std::cout << "overlap\n"; + overlap = true; + break; + } + } + + if (overlap) { + // std::cout << "overlap2\n"; + guess_vert += 1; + continue; + } + + // this triangle is an ear + { + index_t idx0, idx1, idx2; + idx0.vertex_index = ind[0].v_idx; + idx0.normal_index = ind[0].vn_idx; + idx0.texcoord_index = ind[0].vt_idx; + idx1.vertex_index = ind[1].v_idx; + idx1.normal_index = ind[1].vn_idx; + idx1.texcoord_index = ind[1].vt_idx; + idx2.vertex_index = ind[2].v_idx; + idx2.normal_index = ind[2].vn_idx; + idx2.texcoord_index = ind[2].vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + + // remove v1 from the list + size_t removed_vert_index = (guess_vert + 1) % npolys; + while (removed_vert_index + 1 < npolys) { + remainingFace.vertex_indices[removed_vert_index] = + remainingFace.vertex_indices[removed_vert_index + 1]; + removed_vert_index += 1; + } + remainingFace.vertex_indices.pop_back(); + } + + // std::cout << "remainingFace.vi.size = " << + // remainingFace.vertex_indices.size() << "\n"; + if (remainingFace.vertex_indices.size() == 3) { + i0 = remainingFace.vertex_indices[0]; + i1 = remainingFace.vertex_indices[1]; + i2 = remainingFace.vertex_indices[2]; + { + index_t idx0, idx1, idx2; + idx0.vertex_index = i0.v_idx; + idx0.normal_index = i0.vn_idx; + idx0.texcoord_index = i0.vt_idx; + idx1.vertex_index = i1.v_idx; + idx1.normal_index = i1.vn_idx; + idx1.texcoord_index = i1.vt_idx; + idx2.vertex_index = i2.v_idx; + idx2.normal_index = i2.vn_idx; + idx2.texcoord_index = i2.vt_idx; + + shape->mesh.indices.push_back(idx0); + shape->mesh.indices.push_back(idx1); + shape->mesh.indices.push_back(idx2); + + shape->mesh.num_face_vertices.push_back(3); + shape->mesh.material_ids.push_back(material_id); + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); + } + } +#endif + } // npolys + } else { + for (size_t k = 0; k < npolys; k++) { + index_t idx; + idx.vertex_index = face.vertex_indices[k].v_idx; + idx.normal_index = face.vertex_indices[k].vn_idx; + idx.texcoord_index = face.vertex_indices[k].vt_idx; + shape->mesh.indices.push_back(idx); + } + + shape->mesh.num_face_vertices.push_back( + static_cast(npolys)); + shape->mesh.material_ids.push_back(material_id); // per face + shape->mesh.smoothing_group_ids.push_back( + face.smoothing_group_id); // per face + } + } + + shape->mesh.tags = tags; + } + + // line + if (!prim_group.lineGroup.empty()) { + // Flatten indices + for (size_t i = 0; i < prim_group.lineGroup.size(); i++) { + for (size_t j = 0; j < prim_group.lineGroup[i].vertex_indices.size(); + j++) { + const vertex_index_t &vi = prim_group.lineGroup[i].vertex_indices[j]; + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + shape->lines.indices.push_back(idx); + } + + shape->lines.num_line_vertices.push_back( + int(prim_group.lineGroup[i].vertex_indices.size())); + } + } + + // points + if (!prim_group.pointsGroup.empty()) { + // Flatten & convert indices + for (size_t i = 0; i < prim_group.pointsGroup.size(); i++) { + for (size_t j = 0; j < prim_group.pointsGroup[i].vertex_indices.size(); + j++) { + const vertex_index_t &vi = prim_group.pointsGroup[i].vertex_indices[j]; + + index_t idx; + idx.vertex_index = vi.v_idx; + idx.normal_index = vi.vn_idx; + idx.texcoord_index = vi.vt_idx; + + shape->points.indices.push_back(idx); + } + } + } + + return true; +} + +// Split a string with specified delimiter character and escape character. +// https://rosettacode.org/wiki/Tokenize_a_string_with_escaping#C.2B.2B +static void SplitString(const std::string &s, char delim, char escape, + std::vector &elems) { + std::string token; + + bool escaping = false; + for (size_t i = 0; i < s.size(); ++i) { + char ch = s[i]; + if (escaping) { + escaping = false; + } else if (ch == escape) { + escaping = true; + continue; + } else if (ch == delim) { + if (!token.empty()) { + elems.push_back(token); + } + token.clear(); + continue; + } + token += ch; + } + + elems.push_back(token); +} + +static std::string JoinPath(const std::string &dir, + const std::string &filename) { + if (dir.empty()) { + return filename; + } else { + // check '/' + char lastChar = *dir.rbegin(); + if (lastChar != '/') { + return dir + std::string("/") + filename; + } else { + return dir + filename; + } + } +} + +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning, std::string *err) { + (void)err; + + // Create a default material anyway. + material_t material; + InitMaterial(&material); + + // Issue 43. `d` wins against `Tr` since `Tr` is not in the MTL specification. + bool has_d = false; + bool has_tr = false; + + // has_kd is used to set a default diffuse value when map_Kd is present + // and Kd is not. + bool has_kd = false; + + std::stringstream warn_ss; + + size_t line_no = 0; + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + line_no++; + + // 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') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // new mtl + if ((0 == strncmp(token, "newmtl", 6)) && IS_SPACE((token[6]))) { + // flush previous material. + if (!material.name.empty()) { + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); + } + + // initial temporary material + InitMaterial(&material); + + has_d = false; + has_tr = false; + + // set new mtl name + token += 7; + { + std::stringstream sstr; + sstr << token; + material.name = sstr.str(); + } + continue; + } + + // ambient + if (token[0] == 'K' && token[1] == 'a' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.ambient[0] = r; + material.ambient[1] = g; + material.ambient[2] = b; + continue; + } + + // diffuse + if (token[0] == 'K' && token[1] == 'd' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.diffuse[0] = r; + material.diffuse[1] = g; + material.diffuse[2] = b; + has_kd = true; + continue; + } + + // specular + if (token[0] == 'K' && token[1] == 's' && IS_SPACE((token[2]))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.specular[0] = r; + material.specular[1] = g; + material.specular[2] = b; + continue; + } + + // transmittance + if ((token[0] == 'K' && token[1] == 't' && IS_SPACE((token[2]))) || + (token[0] == 'T' && token[1] == 'f' && IS_SPACE((token[2])))) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.transmittance[0] = r; + material.transmittance[1] = g; + material.transmittance[2] = b; + continue; + } + + // ior(index of refraction) + if (token[0] == 'N' && token[1] == 'i' && IS_SPACE((token[2]))) { + token += 2; + material.ior = parseReal(&token); + continue; + } + + // emission + if (token[0] == 'K' && token[1] == 'e' && IS_SPACE(token[2])) { + token += 2; + real_t r, g, b; + parseReal3(&r, &g, &b, &token); + material.emission[0] = r; + material.emission[1] = g; + material.emission[2] = b; + continue; + } + + // shininess + if (token[0] == 'N' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.shininess = parseReal(&token); + continue; + } + + // illum model + if (0 == strncmp(token, "illum", 5) && IS_SPACE(token[5])) { + token += 6; + material.illum = parseInt(&token); + continue; + } + + // dissolve + if ((token[0] == 'd' && IS_SPACE(token[1]))) { + token += 1; + material.dissolve = parseReal(&token); + + if (has_tr) { + warn_ss << "Both `d` and `Tr` parameters defined for \"" + << material.name + << "\". Use the value of `d` for dissolve (line " << line_no + << " in .mtl.)\n"; + } + has_d = true; + continue; + } + if (token[0] == 'T' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + if (has_d) { + // `d` wins. Ignore `Tr` value. + warn_ss << "Both `d` and `Tr` parameters defined for \"" + << material.name + << "\". Use the value of `d` for dissolve (line " << line_no + << " in .mtl.)\n"; + } else { + // We invert value of Tr(assume Tr is in range [0, 1]) + // NOTE: Interpretation of Tr is application(exporter) dependent. For + // some application(e.g. 3ds max obj exporter), Tr = d(Issue 43) + material.dissolve = static_cast(1.0) - parseReal(&token); + } + has_tr = true; + continue; + } + + // PBR: roughness + if (token[0] == 'P' && token[1] == 'r' && IS_SPACE(token[2])) { + token += 2; + material.roughness = parseReal(&token); + continue; + } + + // PBR: metallic + if (token[0] == 'P' && token[1] == 'm' && IS_SPACE(token[2])) { + token += 2; + material.metallic = parseReal(&token); + continue; + } + + // PBR: sheen + if (token[0] == 'P' && token[1] == 's' && IS_SPACE(token[2])) { + token += 2; + material.sheen = parseReal(&token); + continue; + } + + // PBR: clearcoat thickness + if (token[0] == 'P' && token[1] == 'c' && IS_SPACE(token[2])) { + token += 2; + material.clearcoat_thickness = parseReal(&token); + continue; + } + + // PBR: clearcoat roughness + if ((0 == strncmp(token, "Pcr", 3)) && IS_SPACE(token[3])) { + token += 4; + material.clearcoat_roughness = parseReal(&token); + continue; + } + + // PBR: anisotropy + if ((0 == strncmp(token, "aniso", 5)) && IS_SPACE(token[5])) { + token += 6; + material.anisotropy = parseReal(&token); + continue; + } + + // PBR: anisotropy rotation + if ((0 == strncmp(token, "anisor", 6)) && IS_SPACE(token[6])) { + token += 7; + material.anisotropy_rotation = parseReal(&token); + continue; + } + + // ambient texture + if ((0 == strncmp(token, "map_Ka", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.ambient_texname), + &(material.ambient_texopt), token); + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.diffuse_texname), + &(material.diffuse_texopt), token); + + // Set a decent diffuse default value if a diffuse texture is specified + // without a matching Kd value. + if (!has_kd) { + material.diffuse[0] = static_cast(0.6); + material.diffuse[1] = static_cast(0.6); + material.diffuse[2] = static_cast(0.6); + } + + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_texname), + &(material.specular_texopt), token); + continue; + } + + // specular highlight texture + if ((0 == strncmp(token, "map_Ns", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_highlight_texname), + &(material.specular_highlight_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_Bump", 8)) && IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token); + continue; + } + + // alpha texture + if ((0 == strncmp(token, "map_d", 5)) && IS_SPACE(token[5])) { + token += 6; + material.alpha_texname = token; + ParseTextureNameAndOption(&(material.alpha_texname), + &(material.alpha_texopt), token); + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.displacement_texname), + &(material.displacement_texopt), token); + continue; + } + + // reflection map + if ((0 == strncmp(token, "refl", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.reflection_texname), + &(material.reflection_texopt), token); + continue; + } + + // PBR: roughness texture + if ((0 == strncmp(token, "map_Pr", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.roughness_texname), + &(material.roughness_texopt), token); + continue; + } + + // PBR: metallic texture + if ((0 == strncmp(token, "map_Pm", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.metallic_texname), + &(material.metallic_texopt), token); + continue; + } + + // PBR: sheen texture + if ((0 == strncmp(token, "map_Ps", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.sheen_texname), + &(material.sheen_texopt), token); + continue; + } + + // PBR: emissive texture + if ((0 == strncmp(token, "map_Ke", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.emissive_texname), + &(material.emissive_texopt), token); + continue; + } + + // PBR: normal map texture + if ((0 == strncmp(token, "norm", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.normal_texname), + &(material.normal_texopt), token); + continue; + } + + // unknown parameter + const char *_space = strchr(token, ' '); + if (!_space) { + _space = strchr(token, '\t'); + } + if (_space) { + std::ptrdiff_t len = _space - token; + std::string key(token, static_cast(len)); + std::string value = _space + 1; + material.unknown_parameter.insert( + std::pair(key, value)); + } + } + // flush last material. + material_map->insert(std::pair( + material.name, static_cast(materials->size()))); + materials->push_back(material); + + if (warning) { + (*warning) = warn_ss.str(); + } +} + +bool MaterialFileReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *warn, std::string *err) { + if (!m_mtlBaseDir.empty()) { +#ifdef _WIN32 + char sep = ';'; +#else + char sep = ':'; +#endif + + // https://stackoverflow.com/questions/5167625/splitting-a-c-stdstring-using-tokens-e-g + std::vector paths; + std::istringstream f(m_mtlBaseDir); + + std::string s; + while (getline(f, s, sep)) { + paths.push_back(s); + } + + for (size_t i = 0; i < paths.size(); i++) { + std::string filepath = JoinPath(paths[i], matId); + + std::ifstream matIStream(filepath.c_str()); + if (matIStream) { + LoadMtl(matMap, materials, &matIStream, warn, err); + + return true; + } + } + + std::stringstream ss; + ss << "Material file [ " << matId + << " ] not found in a path : " << m_mtlBaseDir << "\n"; + if (warn) { + (*warn) += ss.str(); + } + return false; + + } else { + std::string filepath = matId; + std::ifstream matIStream(filepath.c_str()); + if (matIStream) { + LoadMtl(matMap, materials, &matIStream, warn, err); + + return true; + } + + std::stringstream ss; + ss << "Material file [ " << filepath + << " ] not found in a path : " << m_mtlBaseDir << "\n"; + if (warn) { + (*warn) += ss.str(); + } + + return false; + } +} + +bool MaterialStreamReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *warn, std::string *err) { + (void)err; + (void)matId; + if (!m_inStream) { + std::stringstream ss; + ss << "Material stream in error state. \n"; + if (warn) { + (*warn) += ss.str(); + } + return false; + } + + LoadMtl(matMap, materials, &m_inStream, warn, err); + + return true; +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, const char *filename, const char *mtl_basedir, + bool triangulate, bool default_vcols_fallback) { + attrib->vertices.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + attrib->colors.clear(); + shapes->clear(); + + std::stringstream errss; + + std::ifstream ifs(filename); + if (!ifs) { + errss << "Cannot open file [" << filename << "]\n"; + if (err) { + (*err) = errss.str(); + } + return false; + } + + std::string baseDir = mtl_basedir ? mtl_basedir : ""; + if (!baseDir.empty()) { +#ifndef _WIN32 + const char dirsep = '/'; +#else + const char dirsep = '\\'; +#endif + if (baseDir[baseDir.length() - 1] != dirsep) baseDir += dirsep; + } + MaterialFileReader matFileReader(baseDir); + + return LoadObj(attrib, shapes, materials, warn, err, &ifs, &matFileReader, + triangulate, default_vcols_fallback); +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *warn, + std::string *err, std::istream *inStream, + MaterialReader *readMatFn /*= NULL*/, bool triangulate, + bool default_vcols_fallback) { + std::stringstream errss; + + std::vector v; + std::vector vn; + std::vector vt; + std::vector vc; + std::vector vw; + std::vector tags; + PrimGroup prim_group; + std::string name; + + // material + std::set material_filenames; + std::map material_map; + int material = -1; + + // smoothing group id + unsigned int current_smoothing_id = + 0; // Initial value. 0 means no smoothing. + + int greatest_v_idx = -1; + int greatest_vn_idx = -1; + int greatest_vt_idx = -1; + + shape_t shape; + + bool found_all_colors = true; + + size_t line_num = 0; + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + + line_num++; + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + real_t x, y, z; + real_t r, g, b; + + found_all_colors &= parseVertexWithColor(&x, &y, &z, &r, &g, &b, &token); + + v.push_back(x); + v.push_back(y); + v.push_back(z); + + if (found_all_colors || default_vcols_fallback) { + vc.push_back(r); + vc.push_back(g); + vc.push_back(b); + } + + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; + parseReal3(&x, &y, &z, &token); + vn.push_back(x); + vn.push_back(y); + vn.push_back(z); + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y; + parseReal2(&x, &y, &token); + vt.push_back(x); + vt.push_back(y); + continue; + } + + // skin weight. tinyobj extension + if (token[0] == 'v' && token[1] == 'w' && IS_SPACE((token[2]))) { + token += 3; + + // vw ... + // example: + // vw 0 0 0.25 1 0.25 2 0.5 + + // TODO(syoyo): Add syntax check + int vid = 0; + vid = parseInt(&token); + + skin_weight_t sw; + + sw.vertex_id = vid; + + while (!IS_NEW_LINE(token[0])) { + real_t j, w; + // joint_id should not be negative, weight may be negative + // TODO(syoyo): # of elements check + parseReal2(&j, &w, &token, -1.0); + + if (j < static_cast(0)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `vw' line. joint_id is negative. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + joint_and_weight_t jw; + + jw.joint_id = int(j); + jw.weight = w; + + sw.weightValues.push_back(jw); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + vw.push_back(sw); + } + + // line + if (token[0] == 'l' && IS_SPACE((token[1]))) { + token += 2; + + __line_t line; + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `l' line(e.g. zero value for vertex index. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + line.vertex_indices.push_back(vi); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + prim_group.lineGroup.push_back(line); + + continue; + } + + // points + if (token[0] == 'p' && IS_SPACE((token[1]))) { + token += 2; + + __points_t pts; + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `p' line(e.g. zero value for vertex index. " + "line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + pts.vertex_indices.push_back(vi); + + size_t n = strspn(token, " \t\r"); + token += n; + } + + prim_group.pointsGroup.push_back(pts); + + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + face_t face; + + face.smoothing_group_id = current_smoothing_id; + face.vertex_indices.reserve(3); + + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi; + if (!parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2), &vi)) { + if (err) { + std::stringstream ss; + ss << "Failed parse `f' line(e.g. zero value for face index. line " + << line_num << ".)\n"; + (*err) += ss.str(); + } + return false; + } + + greatest_v_idx = greatest_v_idx > vi.v_idx ? greatest_v_idx : vi.v_idx; + greatest_vn_idx = + greatest_vn_idx > vi.vn_idx ? greatest_vn_idx : vi.vn_idx; + greatest_vt_idx = + greatest_vt_idx > vi.vt_idx ? greatest_vt_idx : vi.vt_idx; + + face.vertex_indices.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } + + // replace with emplace_back + std::move on C++11 + prim_group.faceGroup.push_back(face); + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6))) { + token += 6; + std::string namebuf = parseString(&token); + + int newMaterialId = -1; + std::map::const_iterator it = + material_map.find(namebuf); + if (it != material_map.end()) { + newMaterialId = it->second; + } else { + // { error!! material not found } + if (warn) { + (*warn) += "material [ '" + namebuf + "' ] not found in .mtl\n"; + } + } + + if (newMaterialId != material) { + // Create per-face material. Thus we don't add `shape` to `shapes` at + // this time. + // just clear `faceGroup` after `exportGroupsToShape()` call. + exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + prim_group.faceGroup.clear(); + material = newMaterialId; + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { + token += 7; + + std::vector filenames; + SplitString(std::string(token), ' ', '\\', filenames); + + if (filenames.empty()) { + if (warn) { + std::stringstream ss; + ss << "Looks like empty filename for mtllib. Use default " + "material (line " + << line_num << ".)\n"; + + (*warn) += ss.str(); + } + } else { + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + if (material_filenames.count(filenames[s]) > 0) { + found = true; + continue; + } + + std::string warn_mtl; + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), materials, + &material_map, &warn_mtl, &err_mtl); + if (warn && (!warn_mtl.empty())) { + (*warn) += warn_mtl; + } + + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; + } + + if (ok) { + found = true; + material_filenames.insert(filenames[s]); + break; + } + } + + if (!found) { + if (warn) { + (*warn) += + "Failed to load material file(s). Use default " + "material.\n"; + } + } + } + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0) { + shapes->push_back(shape); + } + + shape = shape_t(); + + // material = -1; + prim_group.clear(); + + std::vector names; + + while (!IS_NEW_LINE(token[0])) { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + // names[0] must be 'g' + + if (names.size() < 2) { + // 'g' with empty names + if (warn) { + std::stringstream ss; + ss << "Empty group name. line: " << line_num << "\n"; + (*warn) += ss.str(); + name = ""; + } + } else { + std::stringstream ss; + ss << names[1]; + + // tinyobjloader does not support multiple groups for a primitive. + // Currently we concatinate multiple group names with a space to get + // single group name. + + for (size_t i = 2; i < names.size(); i++) { + ss << " " << names[i]; + } + + name = ss.str(); + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + (void)ret; // return value not used. + + if (shape.mesh.indices.size() > 0 || shape.lines.indices.size() > 0 || + shape.points.indices.size() > 0) { + shapes->push_back(shape); + } + + // material = -1; + prim_group.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + token += 2; + std::stringstream ss; + ss << token; + name = ss.str(); + + continue; + } + + if (token[0] == 't' && IS_SPACE(token[1])) { + const int max_tag_nums = 8192; // FIXME(syoyo): Parameterize. + tag_t tag; + + token += 2; + + tag.name = parseString(&token); + + tag_sizes ts = parseTagTriple(&token); + + if (ts.num_ints < 0) { + ts.num_ints = 0; + } + if (ts.num_ints > max_tag_nums) { + ts.num_ints = max_tag_nums; + } + + if (ts.num_reals < 0) { + ts.num_reals = 0; + } + if (ts.num_reals > max_tag_nums) { + ts.num_reals = max_tag_nums; + } + + if (ts.num_strings < 0) { + ts.num_strings = 0; + } + if (ts.num_strings > max_tag_nums) { + ts.num_strings = max_tag_nums; + } + + tag.intValues.resize(static_cast(ts.num_ints)); + + for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { + tag.intValues[i] = parseInt(&token); + } + + tag.floatValues.resize(static_cast(ts.num_reals)); + for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { + tag.floatValues[i] = parseReal(&token); + } + + tag.stringValues.resize(static_cast(ts.num_strings)); + for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { + tag.stringValues[i] = parseString(&token); + } + + tags.push_back(tag); + + continue; + } + + if (token[0] == 's' && IS_SPACE(token[1])) { + // smoothing group id + token += 2; + + // skip space. + token += strspn(token, " \t"); // skip space + + if (token[0] == '\0') { + continue; + } + + if (token[0] == '\r' || token[1] == '\n') { + continue; + } + + if (strlen(token) >= 3 && token[0] == 'o' && token[1] == 'f' && + token[2] == 'f') { + current_smoothing_id = 0; + } else { + // assume number + int smGroupId = parseInt(&token); + if (smGroupId < 0) { + // parse error. force set to 0. + // FIXME(syoyo): Report warning. + current_smoothing_id = 0; + } else { + current_smoothing_id = static_cast(smGroupId); + } + } + + continue; + } // smoothing group id + + // Ignore unknown command. + } + + // not all vertices have colors, no default colors desired? -> clear colors + if (!found_all_colors && !default_vcols_fallback) { + vc.clear(); + } + + if (greatest_v_idx >= static_cast(v.size() / 3)) { + if (warn) { + std::stringstream ss; + ss << "Vertex indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + if (greatest_vn_idx >= static_cast(vn.size() / 3)) { + if (warn) { + std::stringstream ss; + ss << "Vertex normal indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + if (greatest_vt_idx >= static_cast(vt.size() / 2)) { + if (warn) { + std::stringstream ss; + ss << "Vertex texcoord indices out of bounds (line " << line_num << ".)\n\n"; + (*warn) += ss.str(); + } + } + + bool ret = exportGroupsToShape(&shape, prim_group, tags, material, name, + triangulate, v, warn); + // exportGroupsToShape 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()) { // FIXME(syoyo): Support other prims(e.g. lines) + shapes->push_back(shape); + } + prim_group.clear(); // for safety + + if (err) { + (*err) += errss.str(); + } + + attrib->vertices.swap(v); + attrib->vertex_weights.swap(v); + attrib->normals.swap(vn); + attrib->texcoords.swap(vt); + attrib->texcoord_ws.swap(vt); + attrib->colors.swap(vc); + attrib->skin_weights.swap(vw); + + return true; +} + +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data /*= NULL*/, + MaterialReader *readMatFn /*= NULL*/, + std::string *warn, /* = NULL*/ + std::string *err /*= NULL*/) { + std::stringstream errss; + + // material + std::set material_filenames; + std::map material_map; + int material_id = -1; // -1 = invalid + + std::vector indices; + std::vector materials; + std::vector names; + names.reserve(2); + std::vector names_out; + + std::string linebuf; + while (inStream.peek() != -1) { + safeGetline(inStream, linebuf); + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\n') + linebuf.erase(linebuf.size() - 1); + } + if (linebuf.size() > 0) { + if (linebuf[linebuf.size() - 1] == '\r') + linebuf.erase(linebuf.size() - 1); + } + + // Skip if empty line. + if (linebuf.empty()) { + continue; + } + + // Skip leading space. + const char *token = linebuf.c_str(); + token += strspn(token, " \t"); + + assert(token); + if (token[0] == '\0') continue; // empty line + + if (token[0] == '#') continue; // comment line + + // vertex + if (token[0] == 'v' && IS_SPACE((token[1]))) { + token += 2; + // TODO(syoyo): Support parsing vertex color extension. + real_t 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, w); + } + continue; + } + + // normal + if (token[0] == 'v' && token[1] == 'n' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; + parseReal3(&x, &y, &z, &token); + if (callback.normal_cb) { + callback.normal_cb(user_data, x, y, z); + } + continue; + } + + // texcoord + if (token[0] == 'v' && token[1] == 't' && IS_SPACE((token[2]))) { + token += 3; + real_t x, y, z; // y and z are optional. default = 0.0 + parseReal3(&x, &y, &z, &token); + if (callback.texcoord_cb) { + callback.texcoord_cb(user_data, x, y, z); + } + continue; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + indices.clear(); + while (!IS_NEW_LINE(token[0])) { + vertex_index_t vi = parseRawTriple(&token); + + 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(indices.size())); + } + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + token += 7; + std::stringstream ss; + ss << token; + std::string namebuf = ss.str(); + + int newMaterialId = -1; + std::map::const_iterator it = + material_map.find(namebuf); + if (it != material_map.end()) { + newMaterialId = it->second; + } else { + // { warn!! material not found } + if (warn && (!callback.usemtl_cb)) { + (*warn) += "material [ " + namebuf + " ] not found in .mtl\n"; + } + } + + if (newMaterialId != material_id) { + material_id = newMaterialId; + } + + if (callback.usemtl_cb) { + callback.usemtl_cb(user_data, namebuf.c_str(), material_id); + } + + continue; + } + + // load mtl + if ((0 == strncmp(token, "mtllib", 6)) && IS_SPACE((token[6]))) { + if (readMatFn) { + token += 7; + + std::vector filenames; + SplitString(std::string(token), ' ', '\\', filenames); + + if (filenames.empty()) { + if (warn) { + (*warn) += + "Looks like empty filename for mtllib. Use default " + "material. \n"; + } + } else { + bool found = false; + for (size_t s = 0; s < filenames.size(); s++) { + if (material_filenames.count(filenames[s]) > 0) { + found = true; + continue; + } + + std::string warn_mtl; + std::string err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), &materials, + &material_map, &warn_mtl, &err_mtl); + + if (warn && (!warn_mtl.empty())) { + (*warn) += warn_mtl; // This should be warn message. + } + + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; + } + + if (ok) { + found = true; + material_filenames.insert(filenames[s]); + break; + } + } + + if (!found) { + if (warn) { + (*warn) += + "Failed to load material file(s). Use default " + "material.\n"; + } + } else { + if (callback.mtllib_cb) { + callback.mtllib_cb(user_data, &materials.at(0), + static_cast(materials.size())); + } + } + } + } + + continue; + } + + // group name + if (token[0] == 'g' && IS_SPACE((token[1]))) { + names.clear(); + + while (!IS_NEW_LINE(token[0])) { + std::string str = parseString(&token); + names.push_back(str); + token += strspn(token, " \t\r"); // skip tag + } + + assert(names.size() > 0); + + if (callback.group_cb) { + if (names.size() > 1) { + // create const char* array. + 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, &names_out.at(0), + static_cast(names_out.size())); + + } else { + callback.group_cb(user_data, NULL, 0); + } + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // @todo { multiple object name? } + token += 2; + + std::stringstream ss; + ss << token; + std::string object_name = ss.str(); + + if (callback.object_cb) { + callback.object_cb(user_data, object_name.c_str()); + } + + continue; + } + +#if 0 // @todo + if (token[0] == 't' && IS_SPACE(token[1])) { + tag_t tag; + + token += 2; + std::stringstream ss; + ss << token; + tag.name = ss.str(); + + token += tag.name.size() + 1; + + tag_sizes ts = parseTagTriple(&token); + + tag.intValues.resize(static_cast(ts.num_ints)); + + for (size_t i = 0; i < static_cast(ts.num_ints); ++i) { + tag.intValues[i] = atoi(token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.floatValues.resize(static_cast(ts.num_reals)); + for (size_t i = 0; i < static_cast(ts.num_reals); ++i) { + tag.floatValues[i] = parseReal(&token); + token += strcspn(token, "/ \t\r") + 1; + } + + tag.stringValues.resize(static_cast(ts.num_strings)); + for (size_t i = 0; i < static_cast(ts.num_strings); ++i) { + std::stringstream ss; + ss << token; + tag.stringValues[i] = ss.str(); + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); + } +#endif + + // Ignore unknown command. + } + + if (err) { + (*err) += errss.str(); + } + + return true; +} + +bool ObjReader::ParseFromFile(const std::string &filename, + const ObjReaderConfig &config) { + std::string mtl_search_path; + + if (config.mtl_search_path.empty()) { + // + // split at last '/'(for unixish system) or '\\'(for windows) to get + // the base directory of .obj file + // + size_t pos = filename.find_last_of("/\\"); + if (pos != std::string::npos) { + mtl_search_path = filename.substr(0, pos); + } + } else { + mtl_search_path = config.mtl_search_path; + } + + valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, + filename.c_str(), mtl_search_path.c_str(), + config.triangulate, config.vertex_color); + + return valid_; +} + +bool ObjReader::ParseFromString(const std::string &obj_text, + const std::string &mtl_text, + const ObjReaderConfig &config) { + std::stringbuf obj_buf(obj_text); + std::stringbuf mtl_buf(mtl_text); + + std::istream obj_ifs(&obj_buf); + std::istream mtl_ifs(&mtl_buf); + + MaterialStreamReader mtl_ss(mtl_ifs); + + valid_ = LoadObj(&attrib_, &shapes_, &materials_, &warning_, &error_, + &obj_ifs, &mtl_ss, config.triangulate, config.vertex_color); + + return valid_; +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif +} // namespace tinyobj + +#endif diff --git a/src/bvhTree.cu b/src/bvhTree.cu new file mode 100644 index 00000000..41887a0c --- /dev/null +++ b/src/bvhTree.cu @@ -0,0 +1,101 @@ +#include "bvhTree.h" +#if USE_BVH_FOR_INTERSECTION +#include + +__host__ +BVHNode::BVHNode() + : idx(-1), minCorner(glm::vec3(FLT_MAX)), maxCorner(glm::vec3(FLT_MIN)), isLeaf(false), hasFace(false), axis(0) +{ +} + +__host__ +BVHNode::~BVHNode() +{ +} + +__host__ __device__ +int BVHNode::getLeftChildIdx() const +{ + return idx * 2 + 1; +} + +__host__ __device__ +int BVHNode::getRightChildIdx() const +{ + return idx * 2 + 2; +} + +__host__ +BVHTree::BVHTree() +{ +} + +__host__ +BVHTree::~BVHTree() +{ +} + +// this function modifies the order of faces, so Geom.faceStartIdx may become invalid +__host__ +void BVHTree::build(std::vector& faces) +{ + if (faces.size() == 0) return; + // The maximum depth of nodes if we use equal counts methods to build the tree. + int depth = glm::ceil(glm::log2((float)faces.size())) + 1; + bvhNodes.resize(glm::pow(2, depth) - 1); + + recursiveBuild(0, faces); +} + +__host__ +void BVHTree::recursiveBuild(int nodeIdx, std::vector& faces) +{ + if (nodeIdx >= bvhNodes.size() * 2 - 1) return; + bvhNodes[nodeIdx].idx = nodeIdx; + + if (faces.size() == 0) + { + bvhNodes[nodeIdx].isLeaf = true; + } + + // find the bounding box of the node + bvhNodes[nodeIdx].minCorner = faces[0].minCorner; + bvhNodes[nodeIdx].maxCorner = faces[0].maxCorner; + for (int i = 1; i < faces.size(); i++) + { + bvhNodes[nodeIdx].minCorner = glm::min(bvhNodes[nodeIdx].minCorner, faces[i].minCorner); + bvhNodes[nodeIdx].maxCorner = glm::max(bvhNodes[nodeIdx].maxCorner, faces[i].maxCorner); + } + + if (/*nodeIdx >= (bvhNodes.size() + 1) / 2 - 1 && */faces.size() == 1) + { + bvhNodes[nodeIdx].face = faces[0]; + bvhNodes[nodeIdx].isLeaf = true; + bvhNodes[nodeIdx].hasFace = true; + return; + } + + // estimate split axis by calculating maximum extent + int& axis = bvhNodes[nodeIdx].axis; + axis = 0; + glm::vec3 diagonal = bvhNodes[nodeIdx].maxCorner - bvhNodes[nodeIdx].minCorner; + if (diagonal.x > diagonal.y && diagonal.x > diagonal.z) axis = 0; + else if (diagonal.y > diagonal.z) axis = 1; + else axis = 2; + + // Partition primitives into equally sized subsets + int midIdx = faces.size() / 2; + std::nth_element(&faces[0], &(faces[midIdx]), &(faces[faces.size() - 1]) + 1, + [axis](const Triangle& a, const Triangle& b) + { + return a.centroid[axis] < b.centroid[axis]; + }); + + std::vector leftFaces(faces.begin(), faces.begin() + midIdx); + recursiveBuild(bvhNodes[nodeIdx].getLeftChildIdx(), leftFaces); + + std::vector rightFaces(faces.begin() + midIdx, faces.end()); + recursiveBuild(bvhNodes[nodeIdx].getRightChildIdx(), rightFaces); +} + +#endif \ No newline at end of file diff --git a/src/bvhTree.h b/src/bvhTree.h new file mode 100644 index 00000000..662bebad --- /dev/null +++ b/src/bvhTree.h @@ -0,0 +1,46 @@ +#pragma once + +#include +#include "glm/glm.hpp" +#include "sceneStructs.h" + +#define USE_BVH_FOR_INTERSECTION 1 + +#if USE_BVH_FOR_INTERSECTION + +class BVHNode +{ +public: + int idx; + glm::vec3 minCorner, maxCorner; // The world-space bounds of this node + //glm::vec3 centroid; + bool isLeaf; + bool hasFace; + Triangle face; + + int axis; // split axis + + __host__ BVHNode(); + __host__ ~BVHNode(); + + __host__ __device__ int getLeftChildIdx() const; + __host__ __device__ int getRightChildIdx() const; + +}; + +class BVHTree +{ +public: + //BVHNode* root; + std::vector bvhNodes; + + __host__ BVHTree(); + __host__ ~BVHTree(); + __host__ void build(std::vector& faces); + +private: + __host__ void recursiveBuild(int nodeIdx, std::vector& faces); + +}; + +#endif \ No newline at end of file diff --git a/src/image.cpp b/src/image.cpp index 94051559..e8eba823 100644 --- a/src/image.cpp +++ b/src/image.cpp @@ -19,7 +19,7 @@ void image::setPixel(int x, int y, const glm::vec3 &pixel) { pixels[(y * xSize) + x] = pixel; } -void image::savePNG(const std::string &baseFilename) { +void image::savePNG(const std::string &baseFilename, const std::string filePath) { unsigned char *bytes = new unsigned char[3 * xSize * ySize]; for (int y = 0; y < ySize; y++) { for (int x = 0; x < xSize; x++) { @@ -31,15 +31,15 @@ void image::savePNG(const std::string &baseFilename) { } } - std::string filename = baseFilename + ".png"; + std::string filename = filePath + baseFilename + ".png"; stbi_write_png(filename.c_str(), xSize, ySize, 3, bytes, xSize * 3); std::cout << "Saved " << filename << "." << std::endl; delete[] bytes; } -void image::saveHDR(const std::string &baseFilename) { - std::string filename = baseFilename + ".hdr"; +void image::saveHDR(const std::string &baseFilename, const std::string filePath) { + std::string filename = filePath + baseFilename + ".hdr"; stbi_write_hdr(filename.c_str(), xSize, ySize, 3, (const float *) pixels); std::cout << "Saved " + filename + "." << std::endl; } diff --git a/src/image.h b/src/image.h index ae1b7683..e263b565 100644 --- a/src/image.h +++ b/src/image.h @@ -14,6 +14,6 @@ class image { image(int x, int y); ~image(); void setPixel(int x, int y, const glm::vec3 &pixel); - void savePNG(const std::string &baseFilename); - void saveHDR(const std::string &baseFilename); + void savePNG(const std::string &baseFilename, const std::string filePath = ""); + void saveHDR(const std::string &baseFilename, const std::string filePath = ""); }; diff --git a/src/interactions.h b/src/interactions.h index f969e458..5f2dea1e 100644 --- a/src/interactions.h +++ b/src/interactions.h @@ -76,4 +76,69 @@ void scatterRay( // TODO: implement this. // A basic implementation of pure-diffuse shading will just call the // calculateRandomDirectionInHemisphere defined above. + glm::vec3 reflectDir; + glm::vec3 refractDir; + float etaI = 0.f; + float etaT = 0.f; + float cosTheta = 0.f; + if (m.hasRefractive) + { + cosTheta = glm::dot(pathSegment.ray.direction, normal); + + bool entering = cosTheta < 0.f; + etaI = entering ? 1.f : m.indexOfRefraction; + etaT = entering ? m.indexOfRefraction : 1.f; + if (!entering) normal = -normal; + + refractDir = glm::refract(pathSegment.ray.direction, normal, etaI / etaT); + } + if (m.hasReflective) + { + // specular reflection + reflectDir = glm::reflect(pathSegment.ray.direction, normal); + } + + if (m.hasRefractive && m.hasReflective) + { + float r0 = (etaT - etaI) / (etaT + etaI); + r0 *= r0; + float cosThetaPlus1 = 1.f + cosTheta; + float pReflect = r0 + (1 - r0) * cosThetaPlus1 * cosThetaPlus1 * cosThetaPlus1 * cosThetaPlus1 * cosThetaPlus1; + + thrust::uniform_real_distribution u01(0, 1); + float probability = u01(rng); + if (probability < pReflect) + { + pathSegment.ray.direction = reflectDir; + pathSegment.ray.origin = intersect; + } + else + { + pathSegment.ray.origin = intersect + 0.001f * pathSegment.ray.direction; + pathSegment.ray.direction = refractDir; + } + pathSegment.color *= m.specular.color; + + } + else if (m.hasRefractive) + { + pathSegment.ray.origin = intersect + 0.001f * pathSegment.ray.direction; + pathSegment.ray.direction = refractDir; + //pathSegment.ray.origin = intersect + 0.001f * pathSegment.ray.direction; + pathSegment.color *= m.specular.color; + } + else if (m.hasReflective) + { + pathSegment.ray.direction = reflectDir; + pathSegment.color *= m.specular.color; + pathSegment.ray.origin = intersect; + } + else + { + // pure diffuse reflection + pathSegment.ray.direction = calculateRandomDirectionInHemisphere(normal, rng); + pathSegment.color *= m.color; + pathSegment.ray.origin = intersect; + } + } diff --git a/src/intersections.h b/src/intersections.h index b1504071..bd527519 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -5,6 +5,7 @@ #include "sceneStructs.h" #include "utilities.h" +#include "bvhTree.h" /** * Handy-dandy hash function that provides seeds for random number generation. @@ -24,14 +25,14 @@ __host__ __device__ inline unsigned int utilhash(unsigned int a) { * Compute a point at parameter value `t` on ray `r`. * Falls slightly short so that it doesn't intersect the object it's hitting. */ -__host__ __device__ glm::vec3 getPointOnRay(Ray r, float t) { +__host__ __device__ glm::vec3 getPointOnRay(const Ray &r, float t) { return r.origin + (t - .0001f) * glm::normalize(r.direction); } /** * Multiplies a mat4 and a vec4 and returns a vec3 clipped from the vec4. */ -__host__ __device__ glm::vec3 multiplyMV(glm::mat4 m, glm::vec4 v) { +__host__ __device__ glm::vec3 multiplyMV(const glm::mat4 &m, const glm::vec4 &v) { return glm::vec3(m * v); } @@ -45,7 +46,7 @@ __host__ __device__ glm::vec3 multiplyMV(glm::mat4 m, glm::vec4 v) { * @param outside Output param for whether the ray came from outside. * @return Ray parameter `t` value. -1 if no intersection. */ -__host__ __device__ float boxIntersectionTest(Geom box, Ray r, +__host__ __device__ float boxIntersectionTest(const Geom &box, const Ray &r, glm::vec3 &intersectionPoint, glm::vec3 &normal, bool &outside) { Ray q; q.origin = multiplyMV(box.inverseTransform, glm::vec4(r.origin , 1.0f)); @@ -99,7 +100,7 @@ __host__ __device__ float boxIntersectionTest(Geom box, Ray r, * @param outside Output param for whether the ray came from outside. * @return Ray parameter `t` value. -1 if no intersection. */ -__host__ __device__ float sphereIntersectionTest(Geom sphere, Ray r, +__host__ __device__ float sphereIntersectionTest(const Geom& sphere, const Ray& r, glm::vec3 &intersectionPoint, glm::vec3 &normal, bool &outside) { float radius = .5; @@ -142,3 +143,240 @@ __host__ __device__ float sphereIntersectionTest(Geom sphere, Ray r, return glm::length(r.origin - intersectionPoint); } + + +// CHECKITOUT +/** + * Test intersection between a ray and a triangle. + * + * @param intersectionPoint Output parameter for point of intersection. + * @return Ray parameter `t` value. -1 if no intersection. + */ +__host__ __device__ float triangleIntersectionTest(const Triangle &triangle, const Ray &r, + glm::vec3& intersectionPoint) +{ + glm::vec3 baryPos; + bool hasIntersect = glm::intersectRayTriangle(r.origin, r.direction, triangle.point1, triangle.point2, triangle.point3, baryPos); + if (!hasIntersect) return -1.f; + + intersectionPoint = (1.f - baryPos.x - baryPos.y) * triangle.point1 + baryPos.x * triangle.point2 + baryPos.y * triangle.point3; + + return glm::length(r.origin - intersectionPoint); +} + +// CHECKITOUT +/** + * Test intersection between a ray and a mesh. + * + * @param intersectionPoint Output parameter for point of intersection. + * @param normal Output parameter for surface normal. + * @param outside Output param for whether the ray came from outside. + * @return Ray parameter `t` value. -1 if no intersection. + */ +__host__ __device__ float meshIntersectionTest(const Geom &mesh, const Triangle* faces, const Ray &r, + glm::vec3& intersectionPoint, glm::vec3& normal, bool& outside) +{ + // get ray in untransformed space + glm::vec3 ro = multiplyMV(mesh.inverseTransform, glm::vec4(r.origin, 1.0f)); + glm::vec3 rd = glm::normalize(multiplyMV(mesh.inverseTransform, glm::vec4(r.direction, 0.0f))); + + Ray rt; + rt.origin = ro; + rt.direction = rd; + + float closestDist = FLT_MAX; + + glm::vec3 currIntersectPoint; + glm::vec3 currNormal; + bool currOutside; + + const Triangle* closestTri = nullptr; + for (int i = mesh.faceStartIdx; i < mesh.faceStartIdx + mesh.faceNum; i++) + { + float t = triangleIntersectionTest(faces[i], rt, currIntersectPoint); + if (t == -1.f) continue; + + if (t < closestDist) + { + closestDist = t; + intersectionPoint = currIntersectPoint; + closestTri = faces + i; + } + } + + if (closestDist != FLT_MAX) + { + // calculate local space normal of the closest triangle + float S = 0.5f * glm::length(glm::cross(closestTri->point1 - closestTri->point2, closestTri->point1 - closestTri->point3)); + float s1 = 0.5f * glm::length(glm::cross(intersectionPoint - closestTri->point2, intersectionPoint - closestTri->point3)) / S; + float s2 = 0.5f * glm::length(glm::cross(intersectionPoint - closestTri->point3, intersectionPoint - closestTri->point1)) / S; + float s3 = 0.5f * glm::length(glm::cross(intersectionPoint - closestTri->point1, intersectionPoint - closestTri->point2)) / S; + normal = closestTri->normal1 * s1 + closestTri->normal2 * s2 + closestTri->normal3 * s3; + + if (glm::dot(rt.direction, normal) <= 0.f) + { + outside = true; + } + else + { + outside = false; + normal = -normal; + } + + // Turn intersection point and normal into global space + intersectionPoint = multiplyMV(mesh.transform, glm::vec4(intersectionPoint, 1.f)); + normal = glm::normalize(multiplyMV(mesh.invTranspose, glm::vec4(normal, 0.f))); + + closestDist = glm::length(r.origin - intersectionPoint); + return closestDist; + } + return -1.f; +} + +#if USE_BVH_FOR_INTERSECTION +__host__ __device__ float boxIntersectionTest(const glm::vec3& minCorner, const glm::vec3& maxCorner, const Ray& r) +{ + // global space intersection test + float tmin = -1e38f; + float tmax = 1e38f; + glm::vec3 tmin_n; + glm::vec3 tmax_n; + for (int xyz = 0; xyz < 3; ++xyz) { + float qdxyz = r.direction[xyz]; + if (glm::abs(qdxyz) > 0.00001f) { + float t1 = (minCorner[xyz] - r.origin[xyz]) / qdxyz; + float t2 = (maxCorner[xyz] - r.origin[xyz]) / qdxyz; + float ta = glm::min(t1, t2); + float tb = glm::max(t1, t2); + glm::vec3 n; + n[xyz] = t2 < t1 ? +1 : -1; + if (ta > 0 && ta > tmin) { + tmin = ta; + tmin_n = n; + } + if (tb < tmax) { + tmax = tb; + tmax_n = n; + } + } + } + + if (tmax >= tmin && tmax > 0) { + return tmin; + } + return -1; +} + +__host__ __device__ float triangleIntersectionTest(const Triangle& triangle, const Geom& mesh, + const Ray& r, glm::vec3& intersectionPoint) +{ + glm::vec3 ro = multiplyMV(mesh.inverseTransform, glm::vec4(r.origin, 1.0f)); + glm::vec3 rd = glm::normalize(multiplyMV(mesh.inverseTransform, glm::vec4(r.direction, 0.0f))); + + Ray rt; + rt.origin = ro; + rt.direction = rd; + + glm::vec3 baryPos; + bool hasIntersect = glm::intersectRayTriangle(rt.origin, rt.direction, triangle.point1, triangle.point2, triangle.point3, baryPos); + if (!hasIntersect) return -1.f; + + intersectionPoint = (1.f - baryPos.x - baryPos.y) * triangle.point1 + baryPos.x * triangle.point2 + baryPos.y * triangle.point3; + + glm::vec3 globalIntersectionPoint = multiplyMV(mesh.transform, glm::vec4(intersectionPoint, 1.f)); + + return glm::length(r.origin - globalIntersectionPoint); +} + +__host__ __device__ float bvhIntersectionTest(const Geom* geoms, const BVHNode* bvhNodes, const int bvhNodes_size, + const Ray& r, glm::vec3& intersectionPoint, glm::vec3& normal, bool& outside, int* hitGeomId) +{ + int currIdx = 0; + + int idxToVisit[64]; + int toVisitOffset = 0; + + idxToVisit[toVisitOffset++] = 0; + + const Triangle* closestTri = nullptr; + float closestDist = FLT_MAX; + + glm::vec3 invDir(1.f / r.direction.x, 1.f / r.direction.y, 1.f / r.direction.z); + bool dirIsNeg[3] = { invDir.x < 0, invDir.y < 0, invDir.z < 0 }; + while (toVisitOffset > 0) + { + currIdx = idxToVisit[--toVisitOffset]; + //if (currIdx >= bvhNodes_size) continue; + + if (!bvhNodes[currIdx].isLeaf) + { + float rayLength = boxIntersectionTest(bvhNodes[currIdx].minCorner, bvhNodes[currIdx].maxCorner, r); + if (rayLength == -1.f || rayLength > closestDist) continue; + int leftIdx = currIdx * 2 + 1; + int rightIdx = leftIdx + 1; + + /*idxToVisit[toVisitOffset++] = rightIdx; + idxToVisit[toVisitOffset++] = leftIdx;*/ + + // Advance to the near node first + if (dirIsNeg[bvhNodes[currIdx].axis]) { + idxToVisit[toVisitOffset++] = leftIdx; + idxToVisit[toVisitOffset++] = rightIdx; + } + else { + idxToVisit[toVisitOffset++] = rightIdx; + idxToVisit[toVisitOffset++] = leftIdx; + } + } + else + { + if (!bvhNodes[currIdx].hasFace) continue; + const Triangle* face = &bvhNodes[currIdx].face; + glm::vec3 localIntersectPos; + float t = triangleIntersectionTest(*face, geoms[face->geomId], r, localIntersectPos); + + if (t != -1.f && closestDist > t) + { + closestTri = &bvhNodes[currIdx].face; + closestDist = t; + intersectionPoint = localIntersectPos; + } + } + } + + if (closestDist != FLT_MAX) + { + *hitGeomId = closestTri->geomId; + + // calculate local space normal of the closest triangle + float S = 0.5f * glm::length(glm::cross(closestTri->point1 - closestTri->point2, closestTri->point1 - closestTri->point3)); + float s1 = 0.5f * glm::length(glm::cross(intersectionPoint - closestTri->point2, intersectionPoint - closestTri->point3)) / S; + float s2 = 0.5f * glm::length(glm::cross(intersectionPoint - closestTri->point3, intersectionPoint - closestTri->point1)) / S; + float s3 = 0.5f * glm::length(glm::cross(intersectionPoint - closestTri->point1, intersectionPoint - closestTri->point2)) / S; + normal = closestTri->normal1 * s1 + closestTri->normal2 * s2 + closestTri->normal3 * s3; + + const Geom* mesh = geoms + closestTri->geomId; + + // Turn intersection point and normal into global space + intersectionPoint = getPointOnRay(r, closestDist); + //intersectionPoint = multiplyMV(mesh->transform, glm::vec4(intersectionPoint, 1.f)); + normal = glm::normalize(multiplyMV(mesh->invTranspose, glm::vec4(normal, 0.f))); + + if (glm::dot(r.direction, normal) <= 0.f) + { + outside = true; + } + else + { + outside = false; + normal = -normal; + } + + closestDist = glm::length(r.origin - intersectionPoint); + return closestDist; + } + + return -1.f; +} + +#endif \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 96127b6d..ebaccb54 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -46,6 +46,7 @@ int main(int argc, char** argv) { //Create Instance for ImGUIData guiData = new GuiDataContainer(); + guiData->TracedDepth = scene->state.traceDepth; // Set up camera stuff from loaded path tracer settings iteration = 0; @@ -102,7 +103,7 @@ void saveImage() { filename = ss.str(); // CHECKITOUT - img.savePNG(filename); + img.savePNG(filename, "../img/"); //img.saveHDR(filename); // Save a Radiance HDR file } diff --git a/src/pathtrace.cu b/src/pathtrace.cu index fd2a4641..e2ea4c23 100644 --- a/src/pathtrace.cu +++ b/src/pathtrace.cu @@ -4,6 +4,8 @@ #include #include #include +#include +#include #include "sceneStructs.h" #include "scene.h" @@ -13,9 +15,15 @@ #include "pathtrace.h" #include "intersections.h" #include "interactions.h" +#include "bvhTree.h" #define ERRORCHECK 1 +#define SORT_BY_MATERIAL 0 +#define ANTIALIASING 1 +#define CACHE_FIRST_INTERSECTION (1 && !ANTIALIASING) + + #define FILENAME (strrchr(__FILE__, '/') ? strrchr(__FILE__, '/') + 1 : __FILE__) #define checkCUDAError(msg) checkCUDAErrorFn(msg, FILENAME, __LINE__) void checkCUDAErrorFn(const char* msg, const char* file, int line) { @@ -71,12 +79,24 @@ static Scene* hst_scene = NULL; static GuiDataContainer* guiData = NULL; static glm::vec3* dev_image = NULL; static Geom* dev_geoms = NULL; + +#if USE_BVH_FOR_INTERSECTION +static BVHNode* dev_bvhNodes = NULL; +static int bvhNodes_size = 0; +#else +static Triangle* dev_faces = NULL; +#endif + static Material* dev_materials = NULL; static PathSegment* dev_paths = NULL; static ShadeableIntersection* dev_intersections = NULL; // TODO: static variables for device memory, any extra info you need, etc // ... +#if CACHE_FIRST_INTERSECTION +static ShadeableIntersection* dev_intersectionsCache = NULL; +#endif + void InitDataContainer(GuiDataContainer* imGuiData) { guiData = imGuiData; @@ -85,6 +105,12 @@ void InitDataContainer(GuiDataContainer* imGuiData) void pathtraceInit(Scene* scene) { hst_scene = scene; +#if USE_BVH_FOR_INTERSECTION + BVHTree bvhTree; + bvhTree.build(hst_scene->faces); + bvhNodes_size = bvhTree.bvhNodes.size(); +#endif + const Camera& cam = hst_scene->state.camera; const int pixelcount = cam.resolution.x * cam.resolution.y; @@ -93,6 +119,14 @@ void pathtraceInit(Scene* scene) { cudaMalloc(&dev_paths, pixelcount * sizeof(PathSegment)); +#if USE_BVH_FOR_INTERSECTION + cudaMalloc(&dev_bvhNodes, bvhTree.bvhNodes.size() * sizeof(BVHNode)); + cudaMemcpy(dev_bvhNodes, bvhTree.bvhNodes.data(), bvhTree.bvhNodes.size() * sizeof(BVHNode), cudaMemcpyHostToDevice); +#else + cudaMalloc(&dev_faces, scene->faces.size() * sizeof(Triangle)); + cudaMemcpy(dev_faces, scene->faces.data(), scene->faces.size() * sizeof(Triangle), cudaMemcpyHostToDevice); +#endif + cudaMalloc(&dev_geoms, scene->geoms.size() * sizeof(Geom)); cudaMemcpy(dev_geoms, scene->geoms.data(), scene->geoms.size() * sizeof(Geom), cudaMemcpyHostToDevice); @@ -103,6 +137,10 @@ void pathtraceInit(Scene* scene) { cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); // TODO: initialize any extra device memeory you need +#if CACHE_FIRST_INTERSECTION + cudaMalloc(&dev_intersectionsCache, pixelcount * sizeof(ShadeableIntersection)); + cudaMemset(dev_intersectionsCache, 0, pixelcount * sizeof(ShadeableIntersection)); +#endif checkCUDAError("pathtraceInit"); } @@ -114,6 +152,15 @@ void pathtraceFree() { cudaFree(dev_materials); cudaFree(dev_intersections); // TODO: clean up any extra device memory you created +#if CACHE_FIRST_INTERSECTION + cudaFree(dev_intersectionsCache); +#endif + +#if USE_BVH_FOR_INTERSECTION + cudaFree(dev_bvhNodes); +#else + cudaFree(dev_faces); +#endif checkCUDAError("pathtraceFree"); } @@ -139,10 +186,23 @@ __global__ void generateRayFromCamera(Camera cam, int iter, int traceDepth, Path segment.color = glm::vec3(1.0f, 1.0f, 1.0f); // TODO: implement antialiasing by jittering the ray +#if ANTIALIASING + thrust::uniform_real_distribution shiftDistGenerator(-0.5f, 0.5f); + thrust::default_random_engine rng = makeSeededRandomEngine(iter, index << 1, 0); + float dx = shiftDistGenerator(rng); + rng = makeSeededRandomEngine(iter, index << 1 + 1, 0); + float dy = shiftDistGenerator(rng); + + segment.ray.direction = glm::normalize(cam.view + - cam.right * cam.pixelLength.x * ((float)x + dx - (float)cam.resolution.x * 0.5f) + - cam.up * cam.pixelLength.y * ((float)y + dy - (float)cam.resolution.y * 0.5f) + ); +#else segment.ray.direction = glm::normalize(cam.view - cam.right * cam.pixelLength.x * ((float)x - (float)cam.resolution.x * 0.5f) - cam.up * cam.pixelLength.y * ((float)y - (float)cam.resolution.y * 0.5f) ); +#endif segment.pixelIndex = index; segment.remainingBounces = traceDepth; @@ -159,6 +219,13 @@ __global__ void computeIntersections( , PathSegment* pathSegments , Geom* geoms , int geoms_size +#if USE_BVH_FOR_INTERSECTION + , BVHNode* bvhNodes + , int bvhNodes_size +#else + , Triangle* faces + , int faces_size +#endif , ShadeableIntersection* intersections ) { @@ -175,6 +242,10 @@ __global__ void computeIntersections( int hit_geom_index = -1; bool outside = true; +#if USE_BVH_FOR_INTERSECTION + bool didBVHIntersection = false; +#endif + glm::vec3 tmp_intersect; glm::vec3 tmp_normal; @@ -193,13 +264,33 @@ __global__ void computeIntersections( t = sphereIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); } // TODO: add more intersection tests here... triangle? metaball? CSG? +#if USE_BVH_FOR_INTERSECTION + else if (!didBVHIntersection && geom.type == MESH) + { + didBVHIntersection = true; + t = bvhIntersectionTest(geoms, bvhNodes, bvhNodes_size, pathSegment.ray, tmp_intersect, tmp_normal, outside, &hit_geom_index); + } + else if (didBVHIntersection && geom.type == MESH) + { + continue; + } +#else + else if (geom.type == MESH) + { + t = meshIntersectionTest(geom, faces, pathSegment.ray, tmp_intersect, tmp_normal, outside); + } +#endif // Compute the minimum t from the intersection tests to determine what // scene geometry object was hit first. if (t > 0.0f && t_min > t) { t_min = t; +#if USE_BVH_FOR_INTERSECTION + if (geom.type != MESH) hit_geom_index = i; +#else hit_geom_index = i; +#endif intersect_point = tmp_intersect; normal = tmp_normal; } @@ -273,6 +364,44 @@ __global__ void shadeFakeMaterial( } } +__global__ void shadeMaterial( + int iter + , int num_paths + , ShadeableIntersection* shadeableIntersections + , PathSegment* pathSegments + , Material* materials) +{ + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx >= num_paths || pathSegments[idx].remainingBounces <= 0) return; + + ShadeableIntersection intersection = shadeableIntersections[idx]; + // If there was no intersection, color the ray black. + if (intersection.t <= 0.0f) + { + pathSegments[idx].color = glm::vec3(0.0f); + pathSegments[idx].remainingBounces = 0; + return; + } + + // if the intersection exists... + Material material = materials[intersection.materialId]; + + // If the material indicates that the object was a light, "light" the ray + if (material.emittance > 0.0f) + { + pathSegments[idx].color *= (material.color * material.emittance); + pathSegments[idx].remainingBounces = 0; + } + // Otherwise, compute the next bounce ray + else + { + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, 0); + glm::vec3 intersect = getPointOnRay(pathSegments[idx].ray, intersection.t); + scatterRay(pathSegments[idx], intersect, intersection.surfaceNormal, material, rng); + pathSegments[idx].remainingBounces--; + } +} + // Add the current iteration's output to the overall image __global__ void finalGather(int nPaths, glm::vec3* image, PathSegment* iterationPaths) { @@ -285,6 +414,24 @@ __global__ void finalGather(int nPaths, glm::vec3* image, PathSegment* iteration } } +struct isRayBouncing +{ + __host__ __device__ + bool operator()(const PathSegment &pathSegment) + { + return pathSegment.remainingBounces > 0; + } +}; + +struct isSmallerMaterialId +{ + __host__ __device__ + bool operator()(const ShadeableIntersection& intersection1, const ShadeableIntersection& intersection2) + { + return intersection1.materialId < intersection2.materialId; + } +}; + /** * Wrapper for the __global__ call that sets up the kernel calls and does a ton * of memory management @@ -341,56 +488,81 @@ void pathtrace(uchar4* pbo, int frame, int iter) { PathSegment* dev_path_end = dev_paths + pixelcount; int num_paths = dev_path_end - dev_paths; + PathSegment* dev_pathsStart = dev_paths; + // --- PathSegment Tracing Stage --- // Shoot ray into scene, bounce between objects, push shading chunks - bool iterationComplete = false; - while (!iterationComplete) { - - // clean shading chunks - cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); - - // tracing - dim3 numblocksPathSegmentTracing = (num_paths + blockSize1d - 1) / blockSize1d; - computeIntersections << > > ( - depth - , num_paths - , dev_paths - , dev_geoms - , hst_scene->geoms.size() - , dev_intersections - ); - checkCUDAError("trace one bounce"); - cudaDeviceSynchronize(); - depth++; + bool iterationComplete = false; + while (!iterationComplete) { - // TODO: - // --- Shading Stage --- - // Shade path segments based on intersections and generate new rays by - // evaluating the BSDF. - // Start off with just a big kernel that handles all the different - // materials you have in the scenefile. - // TODO: compare between directly shading the path segments and shading - // path segments that have been reshuffled to be contiguous in memory. - - shadeFakeMaterial << > > ( - iter, - num_paths, - dev_intersections, - dev_paths, - dev_materials - ); - iterationComplete = true; // TODO: should be based off stream compaction results. - - if (guiData != NULL) + // clean shading chunks + cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); + + // tracing + dim3 numblocksPathSegmentTracing = (num_paths + blockSize1d - 1) / blockSize1d; + +#if CACHE_FIRST_INTERSECTION + if (depth == 0 && iter != 1) { - guiData->TracedDepth = depth; + cudaMemcpy(dev_intersections, dev_intersectionsCache, pixelcount * sizeof(ShadeableIntersection), cudaMemcpyDeviceToDevice); } - } + else + { +#if USE_BVH_FOR_INTERSECTION + computeIntersections <<>> ( + depth, num_paths, dev_paths, dev_geoms, hst_scene->geoms.size(), dev_bvhNodes, bvhNodes_size, dev_intersections); +#else + computeIntersections <<>> ( + depth, num_paths, dev_paths, dev_geoms, hst_scene->geoms.size(), dev_faces, hst_scene->faces.size(), dev_intersections); +#endif + checkCUDAError("trace one bounce"); + cudaDeviceSynchronize(); + } + + if (depth == 0 && iter == 1) + { + cudaMemcpy(dev_intersectionsCache, dev_intersections, pixelcount * sizeof(ShadeableIntersection), cudaMemcpyDeviceToDevice); + } +#else +#if USE_BVH_FOR_INTERSECTION + computeIntersections <<>> ( + depth, num_paths, dev_paths, dev_geoms, hst_scene->geoms.size(), dev_bvhNodes, bvhNodes_size, dev_intersections); +#else + computeIntersections <<>> ( + depth, num_paths, dev_paths, dev_geoms, hst_scene->geoms.size(), dev_faces, hst_scene->faces.size(), dev_intersections); +#endif + checkCUDAError("trace one bounce"); + cudaDeviceSynchronize(); +#endif + + // TODO: + // --- Shading Stage --- + // Shade path segments based on intersections and generate new rays by + // evaluating the BSDF. + // Start off with just a big kernel that handles all the different + // materials you have in the scenefile. + // TODO: compare between directly shading the path segments and shading + // path segments that have been reshuffled to be contiguous in memory. +#if SORT_BY_MATERIAL + thrust::sort_by_key(thrust::device, dev_intersections, dev_intersections + num_paths, dev_paths, isSmallerMaterialId()); +#endif + + shadeMaterial<<>> ( + iter, num_paths, dev_intersections, dev_paths, dev_materials); + + dev_path_end = thrust::partition(thrust::device, dev_paths, dev_path_end, isRayBouncing()); + + num_paths = dev_path_end - dev_paths; + + depth++; + iterationComplete = num_paths == 0; + } - // Assemble this iteration and apply it to the image - dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; - finalGather << > > (num_paths, dev_image, dev_paths); + // Assemble this iteration and apply it to the image + //dev_paths = thrust::raw_pointer_cast(thrust_paths); + dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; + finalGather<<>>(pixelcount, dev_image, dev_paths); /////////////////////////////////////////////////////////////////////////// diff --git a/src/scene.cpp b/src/scene.cpp index 3fb6239a..35b7867f 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -3,6 +3,9 @@ #include #include #include +#include "ThirdPartyLib/tiny_obj_loader.h" + +#define USE_BVH_FOR_INTERSECTION 1 Scene::Scene(string filename) { cout << "Reading scene from " << filename << " ..." << endl; @@ -30,6 +33,16 @@ Scene::Scene(string filename) { } } } +#if USE_BVH_FOR_INTERSECTION + for (Triangle& face : faces) + { + face.computeGlobalBoundingBox(geoms[face.geomId]); + } +#endif +} + +Scene::~Scene() +{ } int Scene::loadGeom(string objectid) { @@ -51,6 +64,10 @@ int Scene::loadGeom(string objectid) { } else if (strcmp(line.c_str(), "cube") == 0) { cout << "Creating new cube..." << endl; newGeom.type = CUBE; + } else { + cout << "Creating new mesh..." << endl; + newGeom.type = MESH; + loadObj(line, newGeom, id); } } @@ -186,3 +203,106 @@ int Scene::loadMaterial(string materialid) { return 1; } } + +int Scene::loadObj(string filePath, Geom& geom, int geomId) +{ + tinyobj::ObjReaderConfig reader_config; + reader_config.mtl_search_path = "./"; // Path to material files + + tinyobj::ObjReader reader; + + if (!reader.ParseFromFile(filePath, reader_config)) { + if (!reader.Error().empty()) { + std::cerr << "TinyObjReader: " << reader.Error(); + } + return -1; + } + + if (!reader.Warning().empty()) { + std::cout << "TinyObjReader: " << reader.Warning(); + } + + cout << "Loading Obj File From " << filePath << "..." << endl; + + auto& attrib = reader.GetAttrib(); + auto& shapes = reader.GetShapes(); + //auto& materials = reader.GetMaterials(); + + geom.faceNum = 0; + for (size_t s = 0; s < shapes.size(); s++) + { + geom.faceNum += shapes[s].mesh.num_face_vertices.size(); + } + + geom.faceStartIdx = faces.size(); + + // Loop over shapes + for (size_t s = 0; s < shapes.size(); s++) { + // Loop over faces(polygon) + size_t index_offset = 0; + for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { + size_t fv = size_t(shapes[s].mesh.num_face_vertices[f]); + + Triangle face; + // Loop over vertices in the face. + for (size_t v = 0; v < fv; v++) { + // access to vertex + tinyobj::index_t idx = shapes[s].mesh.indices[index_offset + v]; + tinyobj::real_t vx = attrib.vertices[3 * size_t(idx.vertex_index) + 0]; + tinyobj::real_t vy = attrib.vertices[3 * size_t(idx.vertex_index) + 1]; + tinyobj::real_t vz = attrib.vertices[3 * size_t(idx.vertex_index) + 2]; + + glm::vec3 vertexPos = glm::vec3(vx, vy, vz); + glm::vec3 vertexNormal; + + // Check if `normal_index` is zero or positive. negative = no normal data + if (idx.normal_index >= 0) { + tinyobj::real_t nx = attrib.normals[3 * size_t(idx.normal_index) + 0]; + tinyobj::real_t ny = attrib.normals[3 * size_t(idx.normal_index) + 1]; + tinyobj::real_t nz = attrib.normals[3 * size_t(idx.normal_index) + 2]; + + vertexNormal = glm::vec3(nx, ny, nz); + } + + // Check if `texcoord_index` is zero or positive. negative = no texcoord data + if (idx.texcoord_index >= 0) { + tinyobj::real_t tx = attrib.texcoords[2 * size_t(idx.texcoord_index) + 0]; + tinyobj::real_t ty = attrib.texcoords[2 * size_t(idx.texcoord_index) + 1]; + } + + // Optional: vertex colors + // tinyobj::real_t red = attrib.colors[3*size_t(idx.vertex_index)+0]; + // tinyobj::real_t green = attrib.colors[3*size_t(idx.vertex_index)+1]; + // tinyobj::real_t blue = attrib.colors[3*size_t(idx.vertex_index)+2]; + + + switch (v) + { + case 0: + face.point1 = vertexPos; + face.normal1 = vertexNormal; + break; + case 1: + face.point2 = vertexPos; + face.normal2 = vertexNormal; + break; + case 2: + face.point3 = vertexPos; + face.normal3 = vertexNormal; + break; + } + } +#if USE_BVH_FOR_INTERSECTION + face.geomId = geomId; + //face.computeLocalBoundingBox(geom); +#endif + faces.push_back(face); + index_offset += fv; + + // per-face material + //shapes[s].mesh.material_ids[f]; + } + } + + return 1; +} diff --git a/src/scene.h b/src/scene.h index f29a9171..55f419a1 100644 --- a/src/scene.h +++ b/src/scene.h @@ -16,11 +16,13 @@ class Scene { int loadMaterial(string materialid); int loadGeom(string objectid); int loadCamera(); + int loadObj(string filePath, Geom &geom, int geomId); public: Scene(string filename); ~Scene(); std::vector geoms; + std::vector faces; std::vector materials; RenderState state; }; diff --git a/src/sceneStructs.h b/src/sceneStructs.h index da4dbf30..6e4f661c 100644 --- a/src/sceneStructs.h +++ b/src/sceneStructs.h @@ -6,10 +6,13 @@ #include "glm/glm.hpp" #define BACKGROUND_COLOR (glm::vec3(0.0f)) +#define USE_BVH_FOR_INTERSECTION 1 enum GeomType { SPHERE, CUBE, + TRIANGLE, + MESH, }; struct Ray { @@ -26,6 +29,38 @@ struct Geom { glm::mat4 transform; glm::mat4 inverseTransform; glm::mat4 invTranspose; + int faceStartIdx; // use with array of Triangle + int faceNum; +}; + +struct Triangle { + glm::vec3 point1; + glm::vec3 point2; + glm::vec3 point3; + glm::vec3 normal1; + glm::vec3 normal2; + glm::vec3 normal3; +#if USE_BVH_FOR_INTERSECTION + int geomId; + glm::vec3 minCorner; + glm::vec3 maxCorner; + glm::vec3 centroid; + __host__ __device__ void computeLocalBoundingBox() + { + minCorner = glm::min(point1, glm::min(point2, point3)); + maxCorner = glm::max(point1, glm::max(point2, point3)); + centroid = (minCorner + maxCorner) * 0.5f; + } + __host__ __device__ void computeGlobalBoundingBox(const Geom& geom) + { + glm::vec3 globalPoint1 = glm::vec3(geom.transform * glm::vec4(point1, 1.0f)); + glm::vec3 globalPoint2 = glm::vec3(geom.transform * glm::vec4(point2, 1.0f)); + glm::vec3 globalPoint3 = glm::vec3(geom.transform * glm::vec4(point3, 1.0f)); + minCorner = glm::min(globalPoint1, glm::min(globalPoint2, globalPoint3)); + maxCorner = glm::max(globalPoint1, glm::max(globalPoint2, globalPoint3)); + centroid = (minCorner + maxCorner) * 0.5f; + } +#endif }; struct Material {