diff --git a/img/bloopers/blooper1.gif b/img/bloopers/blooper1.gif new file mode 100644 index 0000000..4c53ddb Binary files /dev/null and b/img/bloopers/blooper1.gif differ diff --git a/img/charts/DenoiserTimeasPixelsIncrease.png b/img/charts/DenoiserTimeasPixelsIncrease.png new file mode 100644 index 0000000..feaefe6 Binary files /dev/null and b/img/charts/DenoiserTimeasPixelsIncrease.png differ diff --git a/img/charts/HowFilterSizeAffectsPerformance.png b/img/charts/HowFilterSizeAffectsPerformance.png new file mode 100644 index 0000000..ca75479 Binary files /dev/null and b/img/charts/HowFilterSizeAffectsPerformance.png differ diff --git a/img/charts/TotalPathtracerTimevsTotalDenoiserTime.png b/img/charts/TotalPathtracerTimevsTotalDenoiserTime.png new file mode 100644 index 0000000..3d2093e Binary files /dev/null and b/img/charts/TotalPathtracerTimevsTotalDenoiserTime.png differ diff --git a/img/renders/cornell100.png b/img/renders/cornell100.png new file mode 100644 index 0000000..2c72d45 Binary files /dev/null and b/img/renders/cornell100.png differ diff --git a/img/renders/cornell1000.png b/img/renders/cornell1000.png new file mode 100644 index 0000000..16eaa66 Binary files /dev/null and b/img/renders/cornell1000.png differ diff --git a/img/renders/cornell1000_denoised.png b/img/renders/cornell1000_denoised.png new file mode 100644 index 0000000..059f361 Binary files /dev/null and b/img/renders/cornell1000_denoised.png differ diff --git a/img/renders/cornell100_denoised.png b/img/renders/cornell100_denoised.png new file mode 100644 index 0000000..9deb6df Binary files /dev/null and b/img/renders/cornell100_denoised.png differ diff --git a/img/renders/filter10.png b/img/renders/filter10.png new file mode 100644 index 0000000..90ffd6f Binary files /dev/null and b/img/renders/filter10.png differ diff --git a/img/renders/filter160.png b/img/renders/filter160.png new file mode 100644 index 0000000..787f52a Binary files /dev/null and b/img/renders/filter160.png differ diff --git a/img/renders/filter20.png b/img/renders/filter20.png new file mode 100644 index 0000000..de9c867 Binary files /dev/null and b/img/renders/filter20.png differ diff --git a/img/renders/filter320.png b/img/renders/filter320.png new file mode 100644 index 0000000..9b59325 Binary files /dev/null and b/img/renders/filter320.png differ diff --git a/img/renders/filter40.png b/img/renders/filter40.png new file mode 100644 index 0000000..64ccc8c Binary files /dev/null and b/img/renders/filter40.png differ diff --git a/img/renders/filter640.png b/img/renders/filter640.png new file mode 100644 index 0000000..4fc9c99 Binary files /dev/null and b/img/renders/filter640.png differ diff --git a/img/renders/filter80.png b/img/renders/filter80.png new file mode 100644 index 0000000..1ebc096 Binary files /dev/null and b/img/renders/filter80.png differ diff --git a/img/renders/it10.png b/img/renders/it10.png new file mode 100644 index 0000000..0b35e30 Binary files /dev/null and b/img/renders/it10.png differ diff --git a/img/renders/it100.png b/img/renders/it100.png new file mode 100644 index 0000000..c301593 Binary files /dev/null and b/img/renders/it100.png differ diff --git a/img/renders/it1000.png b/img/renders/it1000.png new file mode 100644 index 0000000..3f679f3 Binary files /dev/null and b/img/renders/it1000.png differ diff --git a/img/renders/it10000.png b/img/renders/it10000.png new file mode 100644 index 0000000..d46f452 Binary files /dev/null and b/img/renders/it10000.png differ diff --git a/img/renders/it10000_denoised.png b/img/renders/it10000_denoised.png new file mode 100644 index 0000000..325ef45 Binary files /dev/null and b/img/renders/it10000_denoised.png differ diff --git a/img/renders/it1000_denoised.png b/img/renders/it1000_denoised.png new file mode 100644 index 0000000..4dcd86d Binary files /dev/null and b/img/renders/it1000_denoised.png differ diff --git a/img/renders/it100_denoised.png b/img/renders/it100_denoised.png new file mode 100644 index 0000000..270fb96 Binary files /dev/null and b/img/renders/it100_denoised.png differ diff --git a/img/renders/it10_denoised.png b/img/renders/it10_denoised.png new file mode 100644 index 0000000..688d4db Binary files /dev/null and b/img/renders/it10_denoised.png differ diff --git a/img/renders/it8000.png b/img/renders/it8000.png new file mode 100644 index 0000000..528257e Binary files /dev/null and b/img/renders/it8000.png differ diff --git a/img/renders/mirrors645.png b/img/renders/mirrors645.png new file mode 100644 index 0000000..0c317db Binary files /dev/null and b/img/renders/mirrors645.png differ diff --git a/img/renders/mirrors645_denoised.png b/img/renders/mirrors645_denoised.png new file mode 100644 index 0000000..fd98a98 Binary files /dev/null and b/img/renders/mirrors645_denoised.png differ diff --git a/img/renders/mirrors_normals.png b/img/renders/mirrors_normals.png new file mode 100644 index 0000000..c7aca4a Binary files /dev/null and b/img/renders/mirrors_normals.png differ diff --git a/img/renders/mirrors_positions.png b/img/renders/mirrors_positions.png new file mode 100644 index 0000000..8f9fb99 Binary files /dev/null and b/img/renders/mirrors_positions.png differ diff --git a/img/renders/refraction130.png b/img/renders/refraction130.png new file mode 100644 index 0000000..444de53 Binary files /dev/null and b/img/renders/refraction130.png differ diff --git a/img/renders/refraction130_denoised.png b/img/renders/refraction130_denoised.png new file mode 100644 index 0000000..0aeb29b Binary files /dev/null and b/img/renders/refraction130_denoised.png differ diff --git a/img/renders/refraction5000.png b/img/renders/refraction5000.png new file mode 100644 index 0000000..a7df593 Binary files /dev/null and b/img/renders/refraction5000.png differ diff --git a/img/renders/refraction5000_denoised.png b/img/renders/refraction5000_denoised.png new file mode 100644 index 0000000..5c35ccb Binary files /dev/null and b/img/renders/refraction5000_denoised.png differ diff --git a/img/renders/refraction_normals.png b/img/renders/refraction_normals.png new file mode 100644 index 0000000..6ebc87c Binary files /dev/null and b/img/renders/refraction_normals.png differ diff --git a/img/renders/refraction_positions.png b/img/renders/refraction_positions.png new file mode 100644 index 0000000..c420276 Binary files /dev/null and b/img/renders/refraction_positions.png differ diff --git a/objs/lamp.obj.txt b/objs/lamp.obj.txt new file mode 100644 index 0000000..daee8ee --- /dev/null +++ b/objs/lamp.obj.txt @@ -0,0 +1,1219 @@ +# This file uses centimeters as units for non-parametric coordinates. + +mtllib lamp.mtl +g default +v -0.389275 -1.655988 0.400184 +v 0.389275 -1.655988 0.400184 +v -0.389275 -1.488942 0.400184 +v 0.389275 -1.488942 0.400184 +v -0.389275 -1.488942 -0.429465 +v 0.389275 -1.488942 -0.429465 +v -0.389275 -1.655988 -0.429465 +v 0.389275 -1.655988 -0.429465 +v -0.128371 -1.368280 0.134668 +v 0.128371 -1.368280 0.134668 +v 0.128371 -1.368280 -0.163950 +v -0.128371 -1.368280 -0.163950 +v -0.128371 -0.932430 0.134668 +v 0.128371 -0.932430 0.134668 +v 0.128371 -0.932430 -0.163950 +v -0.128371 -0.932430 -0.163950 +v -0.098908 1.167262 0.100400 +v 0.098908 1.167262 0.100400 +v 0.098908 1.167262 -0.129682 +v -0.098908 1.167262 -0.129682 +v -0.098908 1.628445 0.100400 +v 0.098908 1.628445 0.100400 +v 0.098908 1.628445 -0.129682 +v -0.098908 1.628445 -0.129682 +v 0.204629 -0.653204 -0.240207 +v 0.204629 -0.653204 0.210926 +v -0.204629 -0.653204 0.210926 +v -0.204629 -0.653204 -0.240207 +v -0.065089 -1.170018 0.070610 +v -0.065089 -1.170018 -0.099892 +v 0.065089 -1.170018 -0.099892 +v 0.065089 -1.170018 0.070610 +v -0.202173 0.760933 0.206456 +v -0.202173 0.760933 -0.235737 +v 0.202173 0.760933 -0.235737 +v 0.202173 0.760933 0.206456 +v -0.232834 0.423017 0.239988 +v -0.232834 0.423017 -0.269269 +v 0.232834 0.423017 -0.269269 +v 0.232834 0.423017 0.239988 +v -0.202173 0.085101 0.206456 +v -0.202173 0.085101 -0.235737 +v 0.202173 0.085101 -0.235737 +v 0.202173 0.085101 0.206456 +v -0.054055 -0.207372 0.080264 +v -0.054055 -0.207372 -0.109545 +v 0.054055 -0.207372 -0.109545 +v 0.054055 -0.207372 0.080264 +v -0.071601 -0.393670 0.089593 +v -0.071601 -0.393670 -0.118875 +v 0.071601 -0.393670 -0.118875 +v 0.071601 -0.393670 0.089593 +v 0.204628 1.474718 -0.240207 +v 0.204628 1.474718 0.210926 +v -0.204628 1.474718 0.210926 +v -0.204628 1.474718 -0.240207 +v 0.204628 1.320990 -0.240207 +v 0.204628 1.320990 0.210926 +v -0.204628 1.320990 0.210926 +v -0.204628 1.320990 -0.240207 +v -0.095466 1.028757 -0.128737 +v 0.095466 1.028757 -0.128737 +v 0.095466 1.028757 0.099456 +v -0.095466 1.028757 0.099456 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.875000 0.250000 +vt 0.125000 0.000000 +vt 0.125000 0.250000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.384773 0.258055 +vt 0.614192 0.257500 +vt 0.613695 0.493419 +vt 0.385134 0.493006 +vt 0.387825 0.259465 +vt 0.612809 0.259086 +vt 0.613222 0.490099 +vt 0.388281 0.491393 +vt 0.387265 0.260608 +vt 0.611525 0.259900 +vt 0.611733 0.490088 +vt 0.389571 0.490773 +vt 0.625000 0.500000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.375000 0.500000 +vt 0.379785 0.253943 +vt 0.379961 0.496576 +vt 0.619466 0.496778 +vt 0.619709 0.253672 +vt 0.375000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 0.250000 +vt 0.378544 0.252921 +vt 0.379068 0.497630 +vt 0.621291 0.497449 +vt 0.621081 0.252720 +vt 0.382331 0.256042 +vt 0.383417 0.495097 +vt 0.617327 0.494722 +vt 0.616893 0.255626 +vt 0.625000 0.500000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vt 0.375000 0.500000 +vt 0.387220 0.492168 +vt 0.613894 0.491377 +vt 0.613081 0.257666 +vt 0.386711 0.258018 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn -0.410241 0.836744 0.362715 +vn 0.410241 0.836744 0.362715 +vn 0.410241 0.836744 -0.362715 +vn -0.410241 0.836744 -0.362715 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn 0.000000 0.910401 0.413726 +vn 0.000000 0.910401 0.413726 +vn 0.000000 0.910401 0.413726 +vn 0.000000 0.910401 0.413726 +vn 0.419760 0.907635 0.000000 +vn 0.419760 0.907635 0.000000 +vn 0.419760 0.907635 0.000000 +vn 0.419760 0.907635 0.000000 +vn 0.000000 0.910401 -0.413726 +vn 0.000000 0.910401 -0.413726 +vn 0.000000 0.910401 -0.413726 +vn 0.000000 0.910401 -0.413726 +vn -0.419760 0.907635 0.000000 +vn -0.419760 0.907635 0.000000 +vn -0.419760 0.907635 0.000000 +vn -0.419760 0.907635 0.000000 +vn 0.000000 0.307449 0.951564 +vn 0.000000 0.307449 0.951564 +vn 0.000000 0.002199 0.999998 +vn 0.000000 0.002199 0.999998 +vn 0.952650 0.304069 0.000000 +vn 0.952650 0.304069 0.000000 +vn 0.999998 0.002196 0.000000 +vn 0.999998 0.002196 0.000000 +vn 0.000000 0.307449 -0.951564 +vn 0.000000 0.307449 -0.951564 +vn 0.000000 0.002199 -0.999998 +vn 0.000000 0.002199 -0.999998 +vn -0.952650 0.304069 0.000000 +vn -0.952650 0.304069 0.000000 +vn -0.999998 0.002196 0.000000 +vn -0.999998 0.002196 0.000000 +vn 0.000000 -0.263455 0.964672 +vn 0.000000 -0.263455 0.964672 +vn 0.000000 0.034460 0.999406 +vn 0.000000 0.034460 0.999406 +vn 0.964672 -0.263455 0.000000 +vn 0.964672 -0.263455 0.000000 +vn 0.997714 0.067574 0.000000 +vn 0.997714 0.067574 0.000000 +vn 0.000000 0.034460 -0.999406 +vn 0.000000 -0.263455 -0.964672 +vn 0.000000 -0.263455 -0.964672 +vn 0.000000 0.034460 -0.999406 +vn -0.964672 -0.263455 0.000000 +vn -0.964672 -0.263455 0.000000 +vn -0.997714 0.067574 0.000000 +vn -0.997714 0.067574 0.000000 +vn 0.000000 0.353600 0.935397 +vn 0.000000 0.353600 0.935397 +vn 0.928770 0.370656 0.000000 +vn 0.928770 0.370656 0.000000 +vn 0.000000 -0.193222 0.981155 +vn 0.000000 -0.193222 0.981155 +vn 0.000000 -0.310582 0.950547 +vn 0.000000 -0.310582 0.950547 +vn 0.975420 -0.220355 0.000000 +vn 0.975420 -0.220355 0.000000 +vn 0.944566 -0.328323 0.000000 +vn 0.944566 -0.328323 0.000000 +vn 0.000000 0.291897 0.956450 +vn 0.000000 0.291897 0.956450 +vn 0.000000 0.190326 0.981721 +vn 0.000000 0.190326 0.981721 +vn 0.958577 0.284835 0.000000 +vn 0.958577 0.284835 0.000000 +vn 0.982521 0.186153 0.000000 +vn 0.982521 0.186153 0.000000 +vn 0.000000 -0.583753 0.811931 +vn 0.000000 -0.583753 0.811931 +vn 0.000000 -0.257859 0.966183 +vn 0.000000 -0.257859 0.966183 +vn 0.823961 -0.566646 0.000000 +vn 0.823961 -0.566646 0.000000 +vn 0.968712 -0.248187 0.000000 +vn 0.968712 -0.248187 0.000000 +vn 0.000000 -0.583753 -0.811931 +vn 0.000000 -0.583753 -0.811931 +vn 0.000000 -0.257859 -0.966183 +vn 0.000000 -0.257859 -0.966183 +vn -0.823961 -0.566646 0.000000 +vn -0.823961 -0.566646 0.000000 +vn -0.968712 -0.248187 0.000000 +vn -0.968712 -0.248187 0.000000 +vn -0.928770 0.370656 0.000000 +vn -0.928770 0.370656 0.000000 +vn 0.000000 0.353600 -0.935397 +vn 0.000000 0.353600 -0.935397 +vn -0.966311 -0.257377 0.000000 +vn -0.966311 -0.257377 0.000000 +vn 0.000000 -0.260323 -0.965522 +vn 0.000000 -0.260323 -0.965522 +vn 0.966311 -0.257377 0.000000 +vn 0.966311 -0.257377 0.000000 +vn 0.000000 -0.260323 0.965522 +vn 0.000000 -0.260323 0.965522 +vn -0.958577 0.284835 0.000000 +vn -0.982521 0.186153 0.000000 +vn -0.982521 0.186153 0.000000 +vn -0.958577 0.284835 0.000000 +vn 0.000000 0.291897 -0.956450 +vn 0.000000 0.190326 -0.981721 +vn 0.000000 0.190326 -0.981721 +vn 0.000000 0.291897 -0.956450 +vn -1.000000 -0.000000 0.000000 +vn -1.000000 -0.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 1.000000 -0.000000 0.000000 +vn 1.000000 -0.000000 0.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn -0.975420 -0.220355 0.000000 +vn -0.975420 -0.220355 0.000000 +vn 0.000000 -0.193222 -0.981155 +vn 0.000000 -0.193222 -0.981155 +vn -0.944566 -0.328323 0.000000 +vn -0.944566 -0.328323 0.000000 +vn 0.000000 -0.310582 -0.950547 +vn 0.000000 -0.310582 -0.950547 +vn 0.968712 0.248187 0.000000 +vn 0.968712 0.248187 0.000000 +vn 0.000000 0.257859 0.966183 +vn 0.000000 0.257859 0.966183 +vn -0.968712 0.248187 0.000000 +vn -0.968712 0.248187 0.000000 +vn 0.000000 0.257859 -0.966183 +vn 0.000000 0.257859 -0.966183 +vn 0.000000 -0.006819 -0.999977 +vn 0.000000 -0.006819 -0.999977 +vn 0.999691 -0.024845 0.000000 +vn 0.999691 -0.024845 0.000000 +vn 0.000000 -0.006819 0.999977 +vn 0.000000 -0.006819 0.999977 +vn -0.999691 -0.024845 0.000000 +vn -0.999691 -0.024845 0.000000 +s off +g pCube1 lamp +usemtl initialShadingGroup +f 1/1/1 2/2/2 4/4/3 3/3/4 +s 1 +f 21/27/5 22/28/6 23/29/7 24/30/8 +s off +f 5/5/9 6/6/10 8/8/11 7/7/12 +f 7/7/13 8/8/14 2/10/15 1/9/16 +f 2/2/17 8/11/18 6/12/19 4/4/20 +f 7/13/21 1/1/22 3/3/23 5/14/24 +f 9/15/25 3/3/26 4/4/27 10/16/28 +f 4/4/29 6/6/30 11/17/31 10/16/32 +f 6/6/33 5/5/34 12/18/35 11/17/36 +f 5/5/37 3/3/38 9/15/39 12/18/40 +s 2 +f 9/15/41 10/16/42 32/38/43 29/35/44 +s 3 +f 10/16/45 11/17/46 31/37/47 32/38/48 +s 4 +f 11/17/49 12/18/50 30/36/51 31/37/52 +s 5 +f 12/18/53 9/15/54 29/35/55 30/36/56 +s 6 +f 13/19/57 14/20/58 26/32/59 27/33/60 +s 7 +f 14/20/61 15/21/62 25/31/63 26/32/64 +s 8 +f 25/31/65 15/21/66 16/22/67 28/34/68 +s 9 +f 16/22/69 13/19/70 27/33/71 28/34/72 +s 6 +f 52/58/73 49/55/74 27/33/60 26/32/59 +s 7 +f 51/57/75 52/58/76 26/32/64 25/31/63 +s 6 +f 44/50/77 41/47/78 45/51/79 48/54/80 +s 7 +f 43/49/81 44/50/82 48/54/83 47/53/84 +s 6 +f 63/69/85 64/70/86 33/39/87 36/42/88 +s 7 +f 62/68/89 63/69/90 36/42/91 35/41/92 +s 1 +f 17/23/93 18/24/94 58/64/95 59/65/96 +f 18/24/97 19/25/98 57/63/99 58/64/100 +f 19/25/101 20/26/102 60/66/103 57/63/104 +f 20/26/105 17/23/106 59/65/107 60/66/108 +s 9 +f 50/56/109 28/34/72 27/33/71 49/55/110 +s 8 +f 51/57/111 25/31/65 28/34/68 50/56/112 +s 5 +f 30/36/56 29/35/55 13/19/113 16/22/114 +s 4 +f 31/37/52 30/36/51 16/22/115 15/21/116 +s 3 +f 32/38/48 31/37/47 15/21/117 14/20/118 +s 2 +f 29/35/44 32/38/43 14/20/119 13/19/120 +s 9 +f 61/67/121 34/40/122 33/39/123 64/70/124 +s 8 +f 62/68/125 35/41/126 34/40/127 61/67/128 +s 9 +f 34/40/122 38/44/129 37/43/130 33/39/123 +s 8 +f 35/41/126 39/45/131 38/44/132 34/40/127 +s 7 +f 36/42/91 40/46/133 39/45/134 35/41/92 +s 6 +f 33/39/87 37/43/135 40/46/136 36/42/88 +s 9 +f 38/44/129 42/48/137 41/47/138 37/43/130 +s 8 +f 39/45/131 43/49/139 42/48/140 38/44/132 +s 7 +f 40/46/133 44/50/82 43/49/81 39/45/134 +s 6 +f 37/43/135 41/47/78 44/50/77 40/46/136 +s 9 +f 42/48/137 46/52/141 45/51/142 41/47/138 +s 8 +f 43/49/139 47/53/143 46/52/144 42/48/140 +s 9 +f 50/56/109 49/55/110 45/51/142 46/52/141 +s 8 +f 51/57/111 50/56/112 46/52/144 47/53/143 +s 7 +f 52/58/76 51/57/75 47/53/84 48/54/83 +s 6 +f 49/55/74 52/58/73 48/54/80 45/51/79 +s 1 +f 54/60/145 53/59/146 23/29/7 22/28/6 +f 55/61/147 54/60/148 22/28/6 21/27/5 +f 56/62/149 55/61/150 21/27/5 24/30/8 +f 53/59/151 56/62/152 24/30/8 23/29/7 +f 58/64/100 57/63/99 53/59/146 54/60/145 +f 59/65/96 58/64/95 54/60/148 55/61/147 +f 60/66/108 59/65/107 55/61/150 56/62/149 +f 57/63/104 60/66/103 56/62/152 53/59/151 +s 8 +f 62/68/125 61/67/128 20/26/153 19/25/154 +s 7 +f 63/69/90 62/68/89 19/25/155 18/24/156 +s 6 +f 64/70/86 63/69/85 18/24/157 17/23/158 +s 9 +f 61/67/121 64/70/124 17/23/159 20/26/160 +g default +v -0.044864 1.033335 -0.186580 +v 0.044864 1.033335 -0.186580 +v -0.044864 1.083102 -0.130507 +v 0.044864 1.083102 -0.130507 +v -0.057193 1.225863 -0.274838 +v 0.057193 1.225863 -0.274838 +v -0.057193 1.208107 -0.527637 +v 0.057193 1.208107 -0.527637 +v -0.057193 1.044464 -0.985934 +v 0.057193 1.044464 -0.985934 +v -0.057193 1.139360 -1.436270 +v 0.057193 1.139360 -1.436270 +v -0.057193 1.244723 -1.529282 +v 0.057193 1.244723 -1.529282 +v -0.051184 1.365599 -1.511264 +v 0.051184 1.365599 -1.511264 +v -0.051184 1.402448 -1.573677 +v 0.051184 1.402448 -1.573677 +v -0.057193 1.223334 -1.600285 +v 0.057193 1.223334 -1.600285 +v -0.057193 1.068321 -1.463282 +v 0.057193 1.068321 -1.463282 +v -0.057193 0.968156 -0.985934 +v 0.057193 0.968156 -0.985934 +v -0.057193 1.132660 -0.516549 +v 0.057193 1.132660 -0.516549 +v -0.057193 1.156422 -0.305506 +v 0.057193 1.156422 -0.305506 +v -0.038143 1.427139 -1.416866 +v 0.038114 1.427097 -1.416890 +v 0.038143 1.481854 -1.421532 +v -0.038114 1.481895 -1.421508 +v -0.038129 0.936707 -0.208207 +v 0.038129 0.936707 -0.208207 +v 0.038129 0.866539 -0.193414 +v -0.038129 0.866539 -0.193414 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.281250 +vt 0.625000 0.281250 +vt 0.375000 0.312500 +vt 0.625000 0.312500 +vt 0.375000 0.375000 +vt 0.625000 0.375000 +vt 0.375000 0.437500 +vt 0.625000 0.437500 +vt 0.375000 0.468750 +vt 0.625000 0.468750 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 0.781250 +vt 0.625000 0.781250 +vt 0.375000 0.812500 +vt 0.625000 0.812500 +vt 0.375000 0.875000 +vt 0.625000 0.875000 +vt 0.375000 0.937500 +vt 0.625000 0.937500 +vt 0.375000 0.968750 +vt 0.625000 0.968750 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.843750 0.000000 +vt 0.812500 0.000000 +vt 0.750000 0.000000 +vt 0.687500 0.000000 +vt 0.656250 0.000000 +vt 0.875000 0.250000 +vt 0.843750 0.250000 +vt 0.812500 0.250000 +vt 0.750000 0.250000 +vt 0.687500 0.250000 +vt 0.656250 0.250000 +vt 0.125000 0.000000 +vt 0.156250 0.000000 +vt 0.187500 0.000000 +vt 0.250000 0.000000 +vt 0.312500 0.000000 +vt 0.343750 0.000000 +vt 0.125000 0.250000 +vt 0.156250 0.250000 +vt 0.187500 0.250000 +vt 0.250000 0.250000 +vt 0.312500 0.250000 +vt 0.343750 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vn 0.000000 -0.206290 -0.978491 +vn 0.000000 -0.206290 -0.978491 +vn 0.000000 -0.206290 -0.978491 +vn 0.000000 -0.206290 -0.978491 +vn 0.000000 0.710964 0.703229 +vn 0.000000 0.710964 0.703229 +vn 0.000000 0.962966 0.269623 +vn 0.000000 0.962966 0.269623 +vn 0.000000 0.968969 -0.247181 +vn 0.000000 0.968969 -0.247181 +vn 0.000000 0.997150 -0.075445 +vn 0.000000 0.997150 -0.075445 +vn 0.000000 0.938300 0.345823 +vn 0.000000 0.938300 0.345823 +vn 0.000000 0.331937 0.943302 +vn 0.000000 0.331937 0.943302 +vn 0.000000 -0.147438 0.989071 +vn 0.000000 -0.147438 0.989071 +vn 0.000364 0.084476 0.996425 +vn 0.000364 0.084476 0.996425 +vn 0.000364 0.084476 0.996425 +vn 0.000364 0.084476 0.996425 +vn 0.000000 0.146943 -0.989145 +vn 0.000000 0.146943 -0.989145 +vn 0.000000 -0.329230 -0.944250 +vn 0.000000 -0.329230 -0.944250 +vn 0.000000 -0.923504 -0.383589 +vn 0.000000 -0.923504 -0.383589 +vn 0.000000 -0.997699 0.067803 +vn 0.000000 -0.997699 0.067803 +vn 0.000000 -0.963788 0.266668 +vn 0.000000 -0.963788 0.266668 +vn 0.000000 -0.967348 -0.253453 +vn 0.000000 -0.967348 -0.253453 +vn 0.000000 -0.694847 -0.719157 +vn 0.000000 -0.694847 -0.719157 +vn 0.999827 0.018491 0.002143 +vn 0.998461 0.045229 0.032102 +vn 0.998269 0.046155 0.036457 +vn 0.999831 0.018236 0.002113 +vn 1.000000 -0.000000 0.000000 +vn 1.000000 -0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 0.999590 -0.016844 0.023149 +vn 0.999476 -0.019040 0.026167 +vn 0.998836 -0.039490 0.027697 +vn 0.998239 -0.039309 0.044417 +vn -0.998456 0.045397 0.032001 +vn -0.999827 0.018491 0.002143 +vn -0.999831 0.018236 0.002113 +vn -0.998264 0.046349 0.036336 +vn -1.000000 -0.000000 0.000000 +vn -1.000000 -0.000000 0.000000 +vn -1.000000 -0.000000 0.000000 +vn -1.000000 -0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -0.999590 -0.016844 0.023149 +vn -0.999476 -0.019040 0.026167 +vn -0.998836 -0.039490 0.027697 +vn -0.998239 -0.039309 0.044417 +vn -0.000123 -0.837759 0.546039 +vn -0.000123 -0.837759 0.546040 +vn -0.000123 -0.837759 0.546039 +vn -0.000123 -0.837759 0.546039 +vn 0.995748 0.054207 0.074480 +vn 0.995748 0.054207 0.074480 +vn 0.000142 0.886490 -0.462748 +vn 0.000142 0.886490 -0.462748 +vn 0.000142 0.886490 -0.462748 +vn 0.000142 0.886490 -0.462748 +vn -0.995746 0.054630 0.074205 +vn -0.995746 0.054630 0.074205 +vn 0.000000 0.218416 -0.975856 +vn 0.000000 0.218416 -0.975856 +vn 0.000000 0.218416 -0.975856 +vn 0.000000 0.218416 -0.975856 +vn 0.999139 -0.039872 -0.011479 +vn 0.999139 -0.039872 -0.011479 +vn 0.000000 -0.278946 0.960307 +vn 0.000000 -0.278946 0.960307 +vn 0.000000 -0.278946 0.960307 +vn 0.000000 -0.278946 0.960307 +vn -0.999139 -0.039872 -0.011479 +vn -0.999139 -0.039872 -0.011479 +s off +g lamp pCube2 +usemtl initialShadingGroup +f 97/129/161 98/130/162 99/131/163 100/132/164 +s 1 +f 67/73/165 68/74/166 70/76/167 69/75/168 +f 69/75/168 70/76/167 72/78/169 71/77/170 +f 74/80/171 73/79/172 71/77/170 72/78/169 +f 76/82/173 75/81/174 73/79/172 74/80/171 +f 75/81/174 76/82/173 78/84/175 77/83/176 +f 77/83/176 78/84/175 80/86/177 79/85/178 +s off +f 93/125/179 94/126/180 95/127/181 96/128/182 +s 2 +f 81/87/183 82/88/184 84/90/185 83/89/186 +f 83/89/186 84/90/185 86/92/187 85/91/188 +f 88/94/189 87/93/190 85/91/188 86/92/187 +f 90/96/191 89/95/192 87/93/190 88/94/189 +f 89/95/192 90/96/191 92/98/193 91/97/194 +f 91/97/194 92/98/193 66/100/195 65/99/196 +s 3 +f 84/102/197 82/101/198 80/107/199 78/108/200 +f 86/103/201 84/102/197 78/108/200 76/109/202 +f 76/109/202 74/110/203 88/104/204 86/103/201 +f 74/110/203 72/111/205 90/105/206 88/104/204 +f 92/106/207 90/105/206 72/111/205 70/112/208 +f 66/72/209 92/106/207 70/112/208 68/74/210 +s 4 +f 81/113/211 83/114/212 77/120/213 79/119/214 +f 83/114/212 85/115/215 75/121/216 77/120/213 +f 73/122/217 75/121/216 85/115/215 87/116/218 +f 71/123/219 73/122/217 87/116/218 89/117/220 +f 89/117/220 91/118/221 69/124/222 71/123/219 +f 91/118/221 65/71/223 67/73/224 69/124/222 +s off +f 79/85/225 80/86/226 94/126/227 93/125/228 +s 3 +f 80/86/199 82/88/198 95/127/229 94/126/230 +s off +f 82/88/231 81/87/232 96/128/233 95/127/234 +s 4 +f 81/87/211 79/85/214 93/125/235 96/128/236 +s off +f 65/71/237 66/72/238 98/130/239 97/129/240 +s 3 +f 66/72/209 68/74/210 99/131/241 98/130/242 +s off +f 68/74/243 67/73/244 100/132/245 99/131/246 +s 4 +f 67/73/224 65/71/223 97/129/247 100/132/248 +g default +v 0.044864 1.033335 0.155892 +v -0.044864 1.033335 0.155892 +v 0.044864 1.083102 0.099819 +v -0.044864 1.083102 0.099819 +v 0.057193 1.225863 0.244150 +v -0.057193 1.225863 0.244150 +v 0.057193 1.208107 0.496949 +v -0.057193 1.208107 0.496949 +v 0.057193 1.044464 0.955246 +v -0.057193 1.044464 0.955246 +v 0.057193 1.139360 1.405582 +v -0.057193 1.139360 1.405582 +v 0.057193 1.244723 1.498594 +v -0.057193 1.244723 1.498594 +v 0.051184 1.365599 1.480576 +v -0.051184 1.365599 1.480576 +v 0.051184 1.402448 1.542989 +v -0.051184 1.402448 1.542989 +v 0.057193 1.223334 1.569597 +v -0.057193 1.223334 1.569597 +v 0.057193 1.068321 1.432594 +v -0.057193 1.068321 1.432594 +v 0.057193 0.968156 0.955246 +v -0.057193 0.968156 0.955246 +v 0.057193 1.132660 0.485861 +v -0.057193 1.132660 0.485861 +v 0.057193 1.156422 0.274818 +v -0.057193 1.156422 0.274818 +v 0.038143 1.427139 1.386178 +v -0.038114 1.427097 1.386202 +v -0.038143 1.481854 1.390844 +v 0.038114 1.481895 1.390820 +v 0.038129 0.936707 0.177519 +v -0.038129 0.936707 0.177519 +v -0.038129 0.866539 0.162726 +v 0.038129 0.866539 0.162726 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.281250 +vt 0.625000 0.281250 +vt 0.375000 0.312500 +vt 0.625000 0.312500 +vt 0.375000 0.375000 +vt 0.625000 0.375000 +vt 0.375000 0.437500 +vt 0.625000 0.437500 +vt 0.375000 0.468750 +vt 0.625000 0.468750 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 0.781250 +vt 0.625000 0.781250 +vt 0.375000 0.812500 +vt 0.625000 0.812500 +vt 0.375000 0.875000 +vt 0.625000 0.875000 +vt 0.375000 0.937500 +vt 0.625000 0.937500 +vt 0.375000 0.968750 +vt 0.625000 0.968750 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.843750 0.000000 +vt 0.812500 0.000000 +vt 0.750000 0.000000 +vt 0.687500 0.000000 +vt 0.656250 0.000000 +vt 0.875000 0.250000 +vt 0.843750 0.250000 +vt 0.812500 0.250000 +vt 0.750000 0.250000 +vt 0.687500 0.250000 +vt 0.656250 0.250000 +vt 0.125000 0.000000 +vt 0.156250 0.000000 +vt 0.187500 0.000000 +vt 0.250000 0.000000 +vt 0.312500 0.000000 +vt 0.343750 0.000000 +vt 0.125000 0.250000 +vt 0.156250 0.250000 +vt 0.187500 0.250000 +vt 0.250000 0.250000 +vt 0.312500 0.250000 +vt 0.343750 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 0.750000 +vt 0.375000 0.750000 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.250000 +vn -0.000000 -0.206290 0.978491 +vn -0.000000 -0.206290 0.978491 +vn -0.000000 -0.206290 0.978491 +vn -0.000000 -0.206290 0.978491 +vn 0.000000 0.710964 -0.703229 +vn 0.000000 0.710964 -0.703229 +vn 0.000000 0.962966 -0.269623 +vn 0.000000 0.962966 -0.269623 +vn -0.000000 0.968969 0.247181 +vn -0.000000 0.968969 0.247181 +vn -0.000000 0.997150 0.075445 +vn -0.000000 0.997150 0.075445 +vn 0.000000 0.938300 -0.345823 +vn 0.000000 0.938300 -0.345823 +vn 0.000000 0.331937 -0.943302 +vn 0.000000 0.331937 -0.943302 +vn 0.000000 -0.147438 -0.989071 +vn 0.000000 -0.147438 -0.989071 +vn -0.000364 0.084476 -0.996425 +vn -0.000364 0.084476 -0.996425 +vn -0.000364 0.084476 -0.996425 +vn -0.000364 0.084476 -0.996425 +vn -0.000000 0.146943 0.989145 +vn -0.000000 0.146943 0.989145 +vn -0.000000 -0.329230 0.944250 +vn -0.000000 -0.329230 0.944250 +vn -0.000000 -0.923504 0.383589 +vn -0.000000 -0.923504 0.383589 +vn 0.000000 -0.997699 -0.067803 +vn 0.000000 -0.997699 -0.067803 +vn 0.000000 -0.963788 -0.266668 +vn 0.000000 -0.963788 -0.266668 +vn -0.000000 -0.967348 0.253453 +vn -0.000000 -0.967348 0.253453 +vn -0.000000 -0.694847 0.719157 +vn -0.000000 -0.694847 0.719157 +vn -0.999827 0.018491 -0.002143 +vn -0.998461 0.045229 -0.032102 +vn -0.998269 0.046155 -0.036457 +vn -0.999831 0.018236 -0.002113 +vn -1.000000 -0.000000 -0.000000 +vn -1.000000 -0.000000 -0.000000 +vn -1.000000 0.000000 -0.000000 +vn -1.000000 0.000000 -0.000000 +vn -1.000000 0.000000 -0.000000 +vn -1.000000 0.000000 -0.000000 +vn -0.999590 -0.016844 -0.023149 +vn -0.999476 -0.019040 -0.026167 +vn -0.998836 -0.039490 -0.027697 +vn -0.998239 -0.039309 -0.044417 +vn 0.998456 0.045397 -0.032001 +vn 0.999827 0.018491 -0.002143 +vn 0.999831 0.018236 -0.002113 +vn 0.998264 0.046349 -0.036336 +vn 1.000000 -0.000000 0.000000 +vn 1.000000 -0.000000 0.000000 +vn 1.000000 -0.000000 0.000000 +vn 1.000000 -0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 0.999590 -0.016844 -0.023149 +vn 0.999476 -0.019040 -0.026167 +vn 0.998836 -0.039490 -0.027697 +vn 0.998239 -0.039309 -0.044417 +vn 0.000123 -0.837759 -0.546039 +vn 0.000123 -0.837759 -0.546040 +vn 0.000123 -0.837759 -0.546039 +vn 0.000123 -0.837759 -0.546039 +vn -0.995748 0.054207 -0.074480 +vn -0.995748 0.054207 -0.074480 +vn -0.000142 0.886490 0.462748 +vn -0.000142 0.886490 0.462748 +vn -0.000142 0.886490 0.462748 +vn -0.000142 0.886490 0.462748 +vn 0.995746 0.054630 -0.074205 +vn 0.995746 0.054630 -0.074205 +vn -0.000000 0.218416 0.975856 +vn -0.000000 0.218416 0.975856 +vn -0.000000 0.218416 0.975856 +vn -0.000000 0.218416 0.975856 +vn -0.999139 -0.039872 0.011479 +vn -0.999139 -0.039872 0.011479 +vn 0.000000 -0.278946 -0.960307 +vn 0.000000 -0.278946 -0.960307 +vn 0.000000 -0.278946 -0.960307 +vn 0.000000 -0.278946 -0.960307 +vn 0.999139 -0.039872 0.011479 +vn 0.999139 -0.039872 0.011479 +s off +g lamp pCube3 +usemtl initialShadingGroup +f 133/191/249 134/192/250 135/193/251 136/194/252 +s 1 +f 103/135/253 104/136/254 106/138/255 105/137/256 +f 105/137/256 106/138/255 108/140/257 107/139/258 +f 110/142/259 109/141/260 107/139/258 108/140/257 +f 112/144/261 111/143/262 109/141/260 110/142/259 +f 111/143/262 112/144/261 114/146/263 113/145/264 +f 113/145/264 114/146/263 116/148/265 115/147/266 +s off +f 129/187/267 130/188/268 131/189/269 132/190/270 +s 2 +f 117/149/271 118/150/272 120/152/273 119/151/274 +f 119/151/274 120/152/273 122/154/275 121/153/276 +f 124/156/277 123/155/278 121/153/276 122/154/275 +f 126/158/279 125/157/280 123/155/278 124/156/277 +f 125/157/280 126/158/279 128/160/281 127/159/282 +f 127/159/282 128/160/281 102/162/283 101/161/284 +s 3 +f 120/164/285 118/163/286 116/169/287 114/170/288 +f 122/165/289 120/164/285 114/170/288 112/171/290 +f 112/171/290 110/172/291 124/166/292 122/165/289 +f 110/172/291 108/173/293 126/167/294 124/166/292 +f 128/168/295 126/167/294 108/173/293 106/174/296 +f 102/134/297 128/168/295 106/174/296 104/136/298 +s 4 +f 117/175/299 119/176/300 113/182/301 115/181/302 +f 119/176/300 121/177/303 111/183/304 113/182/301 +f 109/184/305 111/183/304 121/177/303 123/178/306 +f 107/185/307 109/184/305 123/178/306 125/179/308 +f 125/179/308 127/180/309 105/186/310 107/185/307 +f 127/180/309 101/133/311 103/135/312 105/186/310 +s off +f 115/147/313 116/148/314 130/188/315 129/187/316 +s 3 +f 116/148/287 118/150/286 131/189/317 130/188/318 +s off +f 118/150/319 117/149/320 132/190/321 131/189/322 +s 4 +f 117/149/299 115/147/302 129/187/323 132/190/324 +s off +f 101/133/325 102/134/326 134/192/327 133/191/328 +s 3 +f 102/134/297 104/136/298 135/193/329 134/192/330 +s off +f 104/136/331 103/135/332 136/194/333 135/193/334 +s 4 +f 103/135/312 101/133/311 133/191/335 136/194/336 +g default +v -0.138581 1.487154 -1.292883 +v 0.204579 1.487154 -1.292883 +v -0.138581 1.575383 -1.292883 +v 0.204579 1.575383 -1.292883 +v -0.138581 1.575383 -1.636042 +v 0.204579 1.575383 -1.636042 +v -0.138581 1.487154 -1.636042 +v 0.204579 1.487154 -1.636042 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.875000 0.250000 +vt 0.125000 0.000000 +vt 0.125000 0.250000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +s off +g lamp pCube4 +usemtl initialShadingGroup +f 137/195/337 138/196/338 140/198/339 139/197/340 +f 139/197/341 140/198/342 142/200/343 141/199/344 +f 141/199/345 142/200/346 144/202/347 143/201/348 +f 143/201/349 144/202/350 138/204/351 137/203/352 +f 138/196/353 144/205/354 142/206/355 140/198/356 +f 143/207/357 137/195/358 139/197/359 141/208/360 +g default +v -0.138581 1.487154 1.535305 +v 0.204579 1.487154 1.535305 +v -0.138581 1.575383 1.535305 +v 0.204579 1.575383 1.535305 +v -0.138581 1.575383 1.192145 +v 0.204579 1.575383 1.192145 +v -0.138581 1.487154 1.192145 +v 0.204579 1.487154 1.192145 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.875000 0.250000 +vt 0.125000 0.000000 +vt 0.125000 0.250000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 0.000000 1.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 0.000000 -1.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn 1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +vn -1.000000 0.000000 0.000000 +s off +g lamp pCube5 +usemtl initialShadingGroup +f 145/209/361 146/210/362 148/212/363 147/211/364 +f 147/211/365 148/212/366 150/214/367 149/213/368 +f 149/213/369 150/214/370 152/216/371 151/215/372 +f 151/215/373 152/216/374 146/218/375 145/217/376 +f 146/210/377 152/219/378 150/220/379 148/212/380 +f 151/221/381 145/209/382 147/211/383 149/222/384 +g default +v -0.068324 1.584585 -1.363140 +v 0.134322 1.584585 -1.363140 +v -0.135166 2.017795 -1.296297 +v 0.201164 2.017795 -1.296297 +v -0.135166 2.017795 -1.632628 +v 0.201164 2.017795 -1.632628 +v -0.068324 1.584585 -1.565785 +v 0.134322 1.584585 -1.565785 +v 0.215822 1.757869 -1.647286 +v -0.149824 1.757869 -1.647286 +v -0.149824 1.757869 -1.281640 +v 0.215822 1.757869 -1.281640 +v 0.147811 1.931153 -1.579275 +v -0.081813 1.931153 -1.579275 +v -0.081813 1.931153 -1.349650 +v 0.147811 1.931153 -1.349650 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.875000 0.250000 +vt 0.125000 0.000000 +vt 0.125000 0.250000 +vt 0.625000 0.650000 +vt 0.875000 0.100000 +vt 0.125000 0.100000 +vt 0.375000 0.650000 +vt 0.375000 0.100000 +vt 0.625000 0.100000 +vt 0.625000 0.550000 +vt 0.875000 0.200000 +vt 0.125000 0.200000 +vt 0.375000 0.550000 +vt 0.375000 0.200000 +vt 0.625000 0.200000 +vn 0.000000 0.063458 0.997985 +vn 0.000000 0.063458 0.997985 +vn 0.000000 -0.524346 0.851506 +vn 0.000000 -0.524346 0.851506 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 -0.524346 -0.851506 +vn 0.000000 -0.524346 -0.851506 +vn 0.000000 0.063458 -0.997985 +vn 0.000000 0.063458 -0.997985 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.997985 0.063458 0.000000 +vn 0.997985 0.063458 0.000000 +vn 0.851506 -0.524345 0.000000 +vn 0.851506 -0.524345 0.000000 +vn -0.997985 0.063458 0.000000 +vn -0.997985 0.063458 0.000000 +vn -0.851506 -0.524345 0.000000 +vn -0.851506 -0.524345 0.000000 +vn 0.000000 -0.425604 -0.904909 +vn 0.000000 -0.023719 -0.999719 +vn 0.000000 -0.023719 -0.999719 +vn 0.000000 -0.425604 -0.904909 +vn -0.999719 -0.023719 0.000000 +vn -0.999719 -0.023719 0.000000 +vn -0.904909 -0.425604 0.000000 +vn -0.904909 -0.425604 0.000000 +vn 0.000000 -0.023719 0.999719 +vn 0.000000 -0.023719 0.999719 +vn 0.000000 -0.425604 0.904909 +vn 0.000000 -0.425604 0.904909 +vn 0.904909 -0.425604 0.000000 +vn 0.999719 -0.023718 0.000000 +vn 0.999719 -0.023718 0.000000 +vn 0.904909 -0.425604 0.000000 +s 1 +g lamp pCube6 +usemtl initialShadingGroup +f 167/247/385 168/248/386 156/226/387 155/225/388 +s off +f 155/225/389 156/226/390 158/228/391 157/227/392 +s 2 +f 157/227/393 158/228/394 165/243/395 166/246/396 +s off +f 159/229/397 160/230/398 154/232/399 153/231/400 +s 3 +f 168/248/401 165/244/402 158/234/403 156/226/404 +s 4 +f 166/245/405 167/247/406 155/225/407 157/236/408 +s 2 +f 159/229/409 162/240/410 161/237/411 160/230/412 +s 4 +f 163/241/413 162/239/414 159/235/415 153/223/416 +s 1 +f 164/242/417 163/241/418 153/223/419 154/224/420 +s 3 +f 160/233/421 161/238/422 164/242/423 154/224/424 +s 2 +f 162/240/410 166/246/396 165/243/395 161/237/411 +s 4 +f 167/247/406 166/245/405 162/239/414 163/241/413 +s 1 +f 168/248/386 167/247/385 163/241/418 164/242/417 +s 3 +f 161/238/422 165/244/402 168/248/401 164/242/423 +g default +v -0.068324 1.584585 1.457231 +v 0.134322 1.584585 1.457231 +v -0.135166 2.017795 1.524074 +v 0.201164 2.017795 1.524074 +v -0.135166 2.017795 1.187743 +v 0.201164 2.017795 1.187743 +v -0.068324 1.584585 1.254586 +v 0.134322 1.584585 1.254586 +v 0.215822 1.757869 1.173086 +v -0.149824 1.757869 1.173086 +v -0.149824 1.757869 1.538731 +v 0.215822 1.757869 1.538731 +v 0.147811 1.931153 1.241096 +v -0.081813 1.931153 1.241096 +v -0.081813 1.931153 1.470721 +v 0.147811 1.931153 1.470721 +vt 0.375000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.625000 1.000000 +vt 0.875000 0.000000 +vt 0.875000 0.250000 +vt 0.125000 0.000000 +vt 0.125000 0.250000 +vt 0.625000 0.650000 +vt 0.875000 0.100000 +vt 0.125000 0.100000 +vt 0.375000 0.650000 +vt 0.375000 0.100000 +vt 0.625000 0.100000 +vt 0.625000 0.550000 +vt 0.875000 0.200000 +vt 0.125000 0.200000 +vt 0.375000 0.550000 +vt 0.375000 0.200000 +vt 0.625000 0.200000 +vn 0.000000 0.063458 0.997985 +vn 0.000000 0.063458 0.997985 +vn 0.000000 -0.524346 0.851506 +vn 0.000000 -0.524346 0.851506 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 1.000000 0.000000 +vn 0.000000 -0.524346 -0.851506 +vn 0.000000 -0.524346 -0.851506 +vn 0.000000 0.063458 -0.997985 +vn 0.000000 0.063458 -0.997985 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.000000 -1.000000 0.000000 +vn 0.997985 0.063458 0.000000 +vn 0.997985 0.063458 0.000000 +vn 0.851506 -0.524345 0.000000 +vn 0.851506 -0.524345 0.000000 +vn -0.997985 0.063458 0.000000 +vn -0.997985 0.063458 0.000000 +vn -0.851506 -0.524345 0.000000 +vn -0.851506 -0.524345 0.000000 +vn 0.000000 -0.425604 -0.904909 +vn 0.000000 -0.023719 -0.999719 +vn 0.000000 -0.023719 -0.999719 +vn 0.000000 -0.425604 -0.904909 +vn -0.999719 -0.023719 0.000000 +vn -0.999719 -0.023719 0.000000 +vn -0.904909 -0.425604 0.000000 +vn -0.904909 -0.425604 0.000000 +vn 0.000000 -0.023719 0.999719 +vn 0.000000 -0.023719 0.999719 +vn 0.000000 -0.425604 0.904909 +vn 0.000000 -0.425604 0.904909 +vn 0.904909 -0.425604 0.000000 +vn 0.999719 -0.023718 0.000000 +vn 0.999719 -0.023718 0.000000 +vn 0.904909 -0.425604 0.000000 +s 1 +g lamp pCube7 +usemtl initialShadingGroup +f 183/273/425 184/274/426 172/252/427 171/251/428 +s off +f 171/251/429 172/252/430 174/254/431 173/253/432 +s 2 +f 173/253/433 174/254/434 181/269/435 182/272/436 +s off +f 175/255/437 176/256/438 170/258/439 169/257/440 +s 3 +f 184/274/441 181/270/442 174/260/443 172/252/444 +s 4 +f 182/271/445 183/273/446 171/251/447 173/262/448 +s 2 +f 175/255/449 178/266/450 177/263/451 176/256/452 +s 4 +f 179/267/453 178/265/454 175/261/455 169/249/456 +s 1 +f 180/268/457 179/267/458 169/249/459 170/250/460 +s 3 +f 176/259/461 177/264/462 180/268/463 170/250/464 +s 2 +f 178/266/450 182/272/436 181/269/435 177/263/451 +s 4 +f 183/273/446 182/271/445 178/265/454 179/267/453 +s 1 +f 184/274/426 183/273/425 179/267/458 180/268/457 +s 3 +f 177/264/462 181/270/442 184/274/441 180/268/463 \ No newline at end of file diff --git a/scenes/cornell.txt b/scenes/cornell.txt index 83ff820..d6155ea 100644 --- a/scenes/cornell.txt +++ b/scenes/cornell.txt @@ -52,7 +52,7 @@ EMITTANCE 0 CAMERA RES 800 800 FOVY 45 -ITERATIONS 5000 +ITERATIONS 1000 DEPTH 8 FILE cornell EYE 0.0 5 10.5 diff --git a/scenes/cornell_ceiling_light.txt b/scenes/cornell_ceiling_light.txt index 15af5f1..9864e95 100644 --- a/scenes/cornell_ceiling_light.txt +++ b/scenes/cornell_ceiling_light.txt @@ -52,10 +52,10 @@ EMITTANCE 0 CAMERA RES 800 800 FOVY 45 -ITERATIONS 10 +ITERATIONS 10000 DEPTH 8 FILE cornell -EYE 0.0 5 10.5 +EYE 0.0 5 9.5 LOOKAT 0 5 0 UP 0 1 0 diff --git a/scenes/mirrors.txt b/scenes/mirrors.txt new file mode 100644 index 0000000..252304e --- /dev/null +++ b/scenes/mirrors.txt @@ -0,0 +1,206 @@ +// Blue Emissive material (light) +MATERIAL 0 +RGB 1.0 0.5 0.1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 10 + +// White Emissive material (light) +MATERIAL 1 +RGB 1 1 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 1.5 + +// Diffuse white +MATERIAL 2 +RGB .98 .95 .95 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse dark blue +MATERIAL 3 +RGB .08 .12 .3 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Diffuse red +MATERIAL 4 +RGB .85 .35 .35 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Specular white +MATERIAL 5 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB .98 .98 .98 +REFL 1 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Transmissive white +MATERIAL 6 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Diffuse blue +MATERIAL 7 +RGB .5 .5 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Teal Emissive material (light) +MATERIAL 8 +RGB 0.1 0.8 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 1.5 + +// Camera +CAMERA +RES 1200 1200 +FOVY 45 +ITERATIONS 5000 +DEPTH 8 +FILE cornell +EYE 0.0 5 4.4 +LOOKAT 0 5 0 +UP 0 1 0 + + +// Ceiling light +OBJECT 0 +cube +material 1 +TRANS 0 10 1 +ROTAT 0 0 0 +SCALE 2.5 .3 2.5 + +// Floor +OBJECT 1 +cube +material 2 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 10 .01 10 + +// Ceiling +OBJECT 2 +cube +material 2 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 10 10 + +// Back wall +OBJECT 3 +cube +material 5 +TRANS 0 5 -2 +ROTAT 0 85 0 +SCALE .01 10 10 + +// Left wall +OBJECT 4 +cube +material 3 +TRANS -4 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 2 +TRANS 4 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// lamp light 1 +OBJECT 6 +sphere +material 0 +TRANS -3 4.35 0 +ROTAT 0 0 0 +SCALE 0.5 0.5 0.5 + +// lamp light 2 +OBJECT 7 +sphere +material 0 +TRANS -0.1 4.35 0 +ROTAT 0 0 0 +SCALE 0.5 0.5 0.5 + +// mesh +OBJECT 8 +mesh +material 6 +FILENAME ../objs/lamp.obj +TRANS -1.5 2 0 +ROTAT 0 90 0 +SCALE 1 1 1 + +// Back wall behind camera +OBJECT 9 +cube +material 5 +TRANS 0 5 4.5 +ROTAT 0 95 0 +SCALE .01 10 10 + +// Right wall +OBJECT 10 +cube +material 8 +TRANS 4 5 0 +ROTAT 0 0 0 +SCALE .02 2 10 + +// Right wall +OBJECT 11 +cube +material 8 +TRANS 4 8 0 +ROTAT 0 0 0 +SCALE .02 2 10 + +// Right wall +OBJECT 12 +cube +material 8 +TRANS 4 2 0 +ROTAT 0 0 0 +SCALE .02 2 10 \ No newline at end of file diff --git a/scenes/refraction.txt b/scenes/refraction.txt new file mode 100644 index 0000000..26bbdfb --- /dev/null +++ b/scenes/refraction.txt @@ -0,0 +1,217 @@ +// 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 + +// Transmissive white +MATERIAL 5 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 1 +REFRIOR 1.5 +EMITTANCE 0 + +// Diffuse blue +MATERIAL 6 +RGB .5 .5 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 0 + +// Emissive material blue +MATERIAL 7 +RGB 0.1 0.7 1 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 2 + +// Emissive material pink +MATERIAL 8 +RGB 1 0.2 0.2 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 0 +REFRIOR 0 +EMITTANCE 2 + +// Transmissive white +MATERIAL 9 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 1 +REFRIOR 1.333 +EMITTANCE 0 + +// Transmissive white +MATERIAL 10 +RGB .98 .98 .98 +SPECEX 0 +SPECRGB 0 0 0 +REFL 0 +REFR 1 +REFRIOR 1.07 +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 4 .3 4 + +// Floor +OBJECT 1 +cube +material 1 +TRANS 0 0 0 +ROTAT 0 0 0 +SCALE 15 .01 10 + +// Ceiling +OBJECT 2 +cube +material 1 +TRANS 0 10 0 +ROTAT 0 0 90 +SCALE .01 15 10 + +// Back wall +OBJECT 3 +cube +material 6 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .01 10 15 + +// Left wall +OBJECT 4 +cube +material 2 +TRANS -7.5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Right wall +OBJECT 5 +cube +material 3 +TRANS 7.5 5 0 +ROTAT 0 0 0 +SCALE .01 10 10 + +// Sphere +OBJECT 6 +sphere +material 5 +TRANS 0 4 0 +ROTAT 0 0 0 +SCALE 3.5 3.5 3.5 + +// Back wall +OBJECT 7 +cube +material 7 +TRANS 0 5 -5 +ROTAT 0 90 0 +SCALE .02 1 15 + +// Back wall +OBJECT 8 +cube +material 8 +TRANS 0 8 -5 +ROTAT 0 90 0 +SCALE .02 1 15 + +// Back wall +OBJECT 9 +cube +material 8 +TRANS 0 2 -5 +ROTAT 0 90 0 +SCALE .02 1 15 + +// Sphere +OBJECT 10 +sphere +material 9 +TRANS -5 4 0 +ROTAT 0 0 0 +SCALE 3.5 3.5 3.5 + +// Sphere +OBJECT 11 +sphere +material 10 +TRANS 5 4 0 +ROTAT 0 0 0 +SCALE 3.5 3.5 3.5 \ No newline at end of file diff --git a/src/glslUtility.cpp b/src/glslUtility.cpp index 80035d1..47b68cc 100644 --- a/src/glslUtility.cpp +++ b/src/glslUtility.cpp @@ -15,7 +15,8 @@ namespace glslUtility { // embedded passthrough shaders so that default passthrough shaders don't need to be loaded static std::string passthroughVS = - " attribute vec4 Position; \n" + " #version 450\n" + " attribute vec4 Position; \n" " attribute vec2 Texcoords; \n" " varying vec2 v_Texcoords; \n" " \n" @@ -24,6 +25,7 @@ static std::string passthroughVS = " gl_Position = Position; \n" " }"; static std::string passthroughFS = + " #version 450\n" " varying vec2 v_Texcoords; \n" " \n" " uniform sampler2D u_image; \n" diff --git a/src/interactions.h b/src/interactions.h index 144a9f5..908255a 100644 --- a/src/interactions.h +++ b/src/interactions.h @@ -40,23 +40,69 @@ glm::vec3 calculateRandomDirectionInHemisphere( + sin(around) * over * perpendicularDirection2; } -/** - * Simple ray scattering with diffuse and perfect specular support. - */ +__host__ __device__ float getFresnelCoefficient(float eta, float cosTheta, float matIOF) { + // handle total internal reflection + float sinThetaI = sqrt(max(0.f, 1.f - cosTheta * cosTheta)); + float sinThetaT = eta * sinThetaI; + float fresnelCoeff = 1.f; + + cosTheta = abs(cosTheta); + if (sinThetaT < 1) { + + float cosThetaT = sqrt(max(0.f, 1.f - sinThetaT * sinThetaT)); + + float rparl = ((matIOF * cosTheta) - (cosThetaT)) / ((matIOF * cosTheta) + (cosThetaT)); + float rperp = ((cosTheta)-(matIOF * cosThetaT)) / ((cosTheta)+(matIOF * cosThetaT)); + fresnelCoeff = (rparl * rparl + rperp * rperp) / 2.0; + } + return fresnelCoeff; +} + __host__ __device__ void scatterRay( - PathSegment & pathSegment, - glm::vec3 intersect, - glm::vec3 normal, - const Material &m, - thrust::default_random_engine &rng) { - glm::vec3 newDirection; + PathSegment& pathSegment, + glm::vec3 intersect, + glm::vec3 normal, + const Material& m, + thrust::default_random_engine& rng) { + + glm::vec3 newDir; + + // specular surface if (m.hasReflective) { - newDirection = glm::reflect(pathSegment.ray.direction, normal); - } else { - newDirection = calculateRandomDirectionInHemisphere(normal, rng); + newDir = glm::reflect(pathSegment.ray.direction, normal); + } + else if (m.hasRefractive) { + const glm::vec3& wi = pathSegment.ray.direction; + + float cosTheta = dot(normal, wi); + + // incoming direction should be opposite normal direction if entering medium + bool entering = cosTheta < 0; + glm::vec3 faceForwardN = !entering ? -normal : normal; + + // if entering, divide air iof (1.0) by the medium's iof + float eta = entering ? 1.f / m.indexOfRefraction : m.indexOfRefraction; + float fresnelCoeff = getFresnelCoefficient(eta, cosTheta, m.indexOfRefraction); + + thrust::uniform_real_distribution u01(0, 1); + if (u01(rng) < fresnelCoeff) { + newDir = glm::normalize(glm::reflect(pathSegment.ray.direction, normal)); + } + else { + newDir = glm::normalize(glm::refract(wi, faceForwardN, eta)); + } + + pathSegment.ray.origin = intersect + 0.001f * pathSegment.ray.direction; + pathSegment.ray.direction = newDir; + return; + + } + // diffuse surface + else { + newDir = calculateRandomDirectionInHemisphere(normal, rng); } - pathSegment.ray.direction = newDirection; - pathSegment.ray.origin = intersect + (newDirection * 0.0001f); + pathSegment.ray.origin = intersect; + pathSegment.ray.direction = newDir; } diff --git a/src/intersections.h b/src/intersections.h index c3e81f4..f5b3544 100644 --- a/src/intersections.h +++ b/src/intersections.h @@ -6,6 +6,8 @@ #include "sceneStructs.h" #include "utilities.h" +#define USE_BB false + /** * Handy-dandy hash function that provides seeds for random number generation. */ @@ -139,3 +141,82 @@ __host__ __device__ float sphereIntersectionTest(Geom sphere, Ray r, return glm::length(r.origin - intersectionPoint); } + +__host__ __device__ glm::vec3 getNormal(glm::vec3 p1, glm::vec3 p2, glm::vec3 p3) { + glm::vec3 v1 = p2 - p1; + glm::vec3 v2 = p3 - p1; + return glm::normalize(glm::cross(v1, v2)); +} + +__host__ __device__ float meshIntersectionTest(Geom mesh, Ray r, + glm::vec3& intersectionPoint, glm::vec3& normal, bool& outside, Triangle* triangles) { + // NOTE: transforming the ray caused bugs -- instead transformed the triangles + + // create bounding box and test for intersection + int intersectsBB = 0; + + if (USE_BB) { + Geom box; + box.type = CUBE; + box.transform = mesh.boundingBox.transform; + box.inverseTransform = mesh.boundingBox.inverseTransform; + box.invTranspose = mesh.boundingBox.invTranspose; + + intersectsBB = boxIntersectionTest(box, r, intersectionPoint, normal, outside); + } + + // if intersects the bounding box, check against each triangle + if (intersectsBB != -1) { + + glm::vec3 tmp_isect_pt; + float tmin = -1e38f; + float tmax = -1e38f; + glm::vec3 tmin_n; + glm::vec3 tmax_n; + + for (int i = 0; i < mesh.numTriangles; i++) { + + + Triangle tri = triangles[i]; + + glm::vec3 newPt1 = multiplyMV(mesh.transform, glm::vec4(tri.p1, 1.0)); + glm::vec3 newPt2 = multiplyMV(mesh.transform, glm::vec4(tri.p2, 1.0)); + glm::vec3 newPt3 = multiplyMV(mesh.transform, glm::vec4(tri.p3, 1.0)); + + // check if ray intersects triangle + bool intersects = glm::intersectRayTriangle(r.origin, r.direction, newPt1, newPt2, newPt3, tmp_isect_pt); + + if (!intersects) { + continue; + } + + // if this t value is less than tmin, replace + float t = glm::length(r.origin - tmp_isect_pt); + + glm::vec3 n = getNormal(newPt1, newPt2, newPt3); + + if (t > tmax) { + tmax = t; + tmax_n = n; + } + if (i == 0 || t < tmin) { + tmin = t; + tmin_n = n; + } + } + + if (tmin <= 0) { + tmin = tmax; + tmin_n = tmax_n; + } + + if (tmin > 0) { + // transform ray back to find the intersection point + intersectionPoint = getPointOnRay(r, tmin); + normal = tmin_n; + return glm::length(r.origin - intersectionPoint); + } + } + + return -1; +} diff --git a/src/main.cpp b/src/main.cpp index 4092ae4..645c11f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -120,6 +120,9 @@ void saveImage() { //img.saveHDR(filename); // Save a Radiance HDR file } +std::clock_t start; +double duration; + void runCuda() { if (lastLoopIterations != ui_iterations) { lastLoopIterations = ui_iterations; @@ -152,6 +155,8 @@ void runCuda() { if (iteration == 0) { pathtraceFree(); pathtraceInit(scene); + + start = std::clock(); } uchar4 *pbo_dptr = NULL; @@ -162,19 +167,37 @@ void runCuda() { // execute the kernel int frame = 0; - pathtrace(frame, iteration); + pathtrace(frame, iteration, ui_denoise); } if (ui_showGbuffer) { showGBuffer(pbo_dptr); } else { - showImage(pbo_dptr, iteration); + showImage(pbo_dptr, iteration, ui_denoise); } // unmap buffer object cudaGLUnmapBufferObject(pbo); - if (ui_saveAndExit) { + if (iteration == ui_iterations && ui_denoise) { + denoiseImage(ui_filterSize, ui_colorWeight, ui_normalWeight, ui_positionWeight, ui_iterations); + //duration = (std::clock() - start) / (double)CLOCKS_PER_SEC; + + // std::cout << "pathtrace + denoise time: " << duration << '\n'; + //pathtraceFree(); + //cudaDeviceReset(); + //exit(EXIT_SUCCESS); + } + /*if (iteration == ui_iterations) { + duration = (std::clock() - start) / (double)CLOCKS_PER_SEC; + + std::cout << "pathtrace time: " << duration << '\n'; + pathtraceFree(); + cudaDeviceReset(); + exit(EXIT_SUCCESS); + }*/ + + if (ui_saveAndExit) { saveImage(); pathtraceFree(); cudaDeviceReset(); diff --git a/src/pathtrace.cu b/src/pathtrace.cu index 23e5f90..b1c1013 100644 --- a/src/pathtrace.cu +++ b/src/pathtrace.cu @@ -4,6 +4,7 @@ #include #include #include +#include #include "sceneStructs.h" #include "scene.h" @@ -13,12 +14,25 @@ #include "pathtrace.h" #include "intersections.h" #include "interactions.h" +#include #define ERRORCHECK 1 #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) { + +#define SORT_MATERIALS false +#define CACHE_FIRST_BOUNCE false +#define DOF false +#define FOCAL_LEN 4.45f +#define ANTIALIASING false + +#define SHOW_T 0 +#define SHOW_POS 1 +#define SHOW_NOR 2 +#define SHOW_GBUFFER_TYPE 1 + +void checkCUDAErrorFn(const char* msg, const char* file, int line) { #if ERRORCHECK cudaDeviceSynchronize(); cudaError_t err = cudaGetLastError(); @@ -46,7 +60,7 @@ thrust::default_random_engine makeSeededRandomEngine(int iter, int index, int de //Kernel that writes the image to the OpenGL PBO directly. __global__ void sendImageToPBO(uchar4* pbo, glm::ivec2 resolution, - int iter, glm::vec3* image) { + int iter, glm::vec3* image) { int x = (blockIdx.x * blockDim.x) + threadIdx.x; int y = (blockIdx.y * blockDim.y) + threadIdx.y; @@ -55,9 +69,9 @@ __global__ void sendImageToPBO(uchar4* pbo, glm::ivec2 resolution, glm::vec3 pix = image[index]; glm::ivec3 color; - color.x = glm::clamp((int) (pix.x / iter * 255.0), 0, 255); - color.y = glm::clamp((int) (pix.y / iter * 255.0), 0, 255); - color.z = glm::clamp((int) (pix.z / iter * 255.0), 0, 255); + color.x = glm::clamp((int)(pix.x / iter * 255.0), 0, 255); + color.y = glm::clamp((int)(pix.y / iter * 255.0), 0, 255); + color.z = glm::clamp((int)(pix.z / iter * 255.0), 0, 255); // Each thread writes one pixel location in the texture (textel) pbo[index].w = 0; @@ -73,46 +87,126 @@ __global__ void gbufferToPBO(uchar4* pbo, glm::ivec2 resolution, GBufferPixel* g if (x < resolution.x && y < resolution.y) { int index = x + (y * resolution.x); - float timeToIntersect = gBuffer[index].t * 256.0; + if (SHOW_GBUFFER_TYPE == SHOW_T) { + float timeToIntersect = gBuffer[index].t * 256.0; + + pbo[index].w = 0; + pbo[index].x = timeToIntersect; + pbo[index].y = timeToIntersect; + pbo[index].z = timeToIntersect; + } + + else if (SHOW_GBUFFER_TYPE == SHOW_POS) { + glm::vec3 position = glm::normalize(abs(gBuffer[index].pos)) * glm::vec3(255.f); + pbo[index].w = 0; + pbo[index].x = position.x; + pbo[index].y = position.y; + pbo[index].z = position.z; + } + + else { + glm::vec3 normal = gBuffer[index].nor; + normal = abs(glm::vec3(normal.x * 255.f, normal.y * 255.f, normal.z * 255.f)); + pbo[index].w = 1; + pbo[index].x = normal.x; + pbo[index].y = normal.y; + pbo[index].z = normal.z; + } + } +} + + +__global__ void sendDenoisedToPBO(uchar4* pbo, glm::ivec2 resolution, + int iter, glm::vec3* image) { + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; + + if (x < resolution.x && y < resolution.y) { + int index = x + (y * resolution.x); + glm::vec3 pix = image[index]; + + glm::ivec3 color; + color.x = glm::clamp((int)(pix.x * 255.0), 0, 255); + color.y = glm::clamp((int)(pix.y * 255.0), 0, 255); + color.z = glm::clamp((int)(pix.z * 255.0), 0, 255); + + // Each thread writes one pixel location in the texture (textel) pbo[index].w = 0; - pbo[index].x = timeToIntersect; - pbo[index].y = timeToIntersect; - pbo[index].z = timeToIntersect; + pbo[index].x = color.x; + pbo[index].y = color.y; + pbo[index].z = color.z; } } -static Scene * hst_scene = NULL; -static glm::vec3 * dev_image = NULL; -static Geom * dev_geoms = NULL; -static Material * dev_materials = NULL; -static PathSegment * dev_paths = NULL; -static ShadeableIntersection * dev_intersections = NULL; +static Scene* hst_scene = NULL; +static glm::vec3* dev_image = NULL; +static Geom* dev_geoms = NULL; +static Material* dev_materials = NULL; +static PathSegment* dev_paths = NULL; +static ShadeableIntersection* dev_intersections = NULL; +static thrust::device_ptr dev_thrust_alive_paths = NULL; +static PathSegment** dev_alive_paths = NULL; +static PathSegment* dev_first_paths = NULL; +static Triangle* dev_triangles = NULL; static GBufferPixel* dev_gBuffer = NULL; +static float* dev_gaussian_kernel = NULL; +static float* dev_gaussian_offsets = NULL; +static glm::vec3* dev_denoised = NULL; +static glm::vec3* dev_denoised_tmp = NULL; + // TODO: static variables for device memory, any extra info you need, etc // ... -void pathtraceInit(Scene *scene) { +void pathtraceInit(Scene* scene) { hst_scene = scene; - const Camera &cam = hst_scene->state.camera; + const Camera& cam = hst_scene->state.camera; const int pixelcount = cam.resolution.x * cam.resolution.y; cudaMalloc(&dev_image, pixelcount * sizeof(glm::vec3)); cudaMemset(dev_image, 0, pixelcount * sizeof(glm::vec3)); - cudaMalloc(&dev_paths, pixelcount * sizeof(PathSegment)); + cudaMalloc(&dev_paths, pixelcount * sizeof(PathSegment)); + cudaMalloc(&dev_first_paths, pixelcount * sizeof(PathSegment)); - cudaMalloc(&dev_geoms, scene->geoms.size() * sizeof(Geom)); - cudaMemcpy(dev_geoms, scene->geoms.data(), scene->geoms.size() * sizeof(Geom), cudaMemcpyHostToDevice); + cudaMalloc(&dev_alive_paths, pixelcount * sizeof(PathSegment*)); + dev_thrust_alive_paths = thrust::device_ptr(dev_alive_paths); - cudaMalloc(&dev_materials, scene->materials.size() * sizeof(Material)); - cudaMemcpy(dev_materials, scene->materials.data(), scene->materials.size() * sizeof(Material), cudaMemcpyHostToDevice); + for (int i = 0; i < scene->geoms.size(); i++) { + if (scene->geoms[i].type == MESH) { + cudaMalloc(&dev_triangles, scene->geoms[i].numTriangles * sizeof(Triangle)); + cudaMemcpy(dev_triangles, scene->geoms[i].triangles, scene->geoms[i].numTriangles * sizeof(Triangle), cudaMemcpyHostToDevice); + } + } + cudaMalloc(&dev_geoms, scene->geoms.size() * sizeof(Geom)); + cudaMemcpy(dev_geoms, scene->geoms.data(), scene->geoms.size() * sizeof(Geom), cudaMemcpyHostToDevice); - cudaMalloc(&dev_intersections, pixelcount * sizeof(ShadeableIntersection)); - cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); + cudaMalloc(&dev_materials, scene->materials.size() * sizeof(Material)); + cudaMemcpy(dev_materials, scene->materials.data(), scene->materials.size() * sizeof(Material), cudaMemcpyHostToDevice); + + cudaMalloc(&dev_intersections, pixelcount * sizeof(ShadeableIntersection)); + cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); cudaMalloc(&dev_gBuffer, pixelcount * sizeof(GBufferPixel)); + cudaMalloc(&dev_gaussian_kernel, 25 * sizeof(float)); + float kernel[25] = { + 0.003765, 0.015019, 0.023792, 0.015019, 0.003765, + 0.015019, 0.059912, 0.094907, 0.059912, 0.015019, + 0.023792, 0.094907, 0.150342, 0.094907, 0.023792, + 0.015019, 0.059912, 0.094907, 0.059912, 0.015019, + 0.003765, 0.015019, 0.023792, 0.015019, 0.003765, + }; + cudaMemcpy(dev_gaussian_kernel, &kernel[0], 25 * sizeof(float), cudaMemcpyHostToDevice); + + cudaMalloc(&dev_gaussian_offsets, 25 * sizeof(float)); + cudaMemset(dev_gaussian_offsets, 0, 25 * sizeof(float)); + + cudaMalloc(&dev_denoised, pixelcount * sizeof(glm::vec3)); + cudaMemset(dev_denoised, 0, pixelcount * sizeof(glm::vec3)); + cudaMalloc(&dev_denoised_tmp, pixelcount * sizeof(glm::vec3)); + cudaMemset(dev_denoised_tmp, 0, pixelcount * sizeof(glm::vec3)); + // TODO: initialize any extra device memeory you need checkCUDAError("pathtraceInit"); @@ -120,16 +214,36 @@ void pathtraceInit(Scene *scene) { void pathtraceFree() { cudaFree(dev_image); // no-op if dev_image is null - cudaFree(dev_paths); - cudaFree(dev_geoms); - cudaFree(dev_materials); - cudaFree(dev_intersections); - cudaFree(dev_gBuffer); + cudaFree(dev_paths); + cudaFree(dev_geoms); + cudaFree(dev_materials); + cudaFree(dev_intersections); + cudaFree(dev_gaussian_kernel); + cudaFree(dev_gaussian_offsets); + cudaFree(dev_denoised); + cudaFree(dev_denoised_tmp); // TODO: clean up any extra device memory you created checkCUDAError("pathtraceFree"); } +__global__ void generateGBuffer( + int num_paths, + ShadeableIntersection* shadeableIntersections, + PathSegment* pathSegments, + GBufferPixel* gBuffer) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + + if (idx < num_paths) + { + ShadeableIntersection isect = shadeableIntersections[idx]; + Ray ray = pathSegments[idx].ray; + gBuffer[idx].t = isect.t; + gBuffer[idx].pos = ray.origin + glm::vec3(isect.t) * ray.direction; + gBuffer[idx].nor = isect.surfaceNormal; + } +} + /** * Generate PathSegments with rays from the camera through the screen into the * scene, which is the first bounce of rays. @@ -138,296 +252,435 @@ void pathtraceFree() { * motion blur - jitter rays "in time" * lens effect - jitter ray origin positions based on a lens */ -__global__ void generateRayFromCamera(Camera cam, int iter, int traceDepth, PathSegment* pathSegments) +__global__ void generateRayFromCamera(Camera cam, int iter, int traceDepth, PathSegment* pathSegments, PathSegment** aliveSegments) { - int x = (blockIdx.x * blockDim.x) + threadIdx.x; - int y = (blockIdx.y * blockDim.y) + threadIdx.y; - - if (x < cam.resolution.x && y < cam.resolution.y) { - int index = x + (y * cam.resolution.x); - PathSegment & segment = pathSegments[index]; - - segment.ray.origin = cam.position; - segment.color = glm::vec3(1.0f, 1.0f, 1.0f); - - 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) - ); + int x = (blockIdx.x * blockDim.x) + threadIdx.x; + int y = (blockIdx.y * blockDim.y) + threadIdx.y; - segment.pixelIndex = index; - segment.remainingBounces = traceDepth; - } + if (x < cam.resolution.x && y < cam.resolution.y) { + int index = x + (y * cam.resolution.x); + PathSegment& segment = pathSegments[index]; + aliveSegments[index] = &segment; + + thrust::default_random_engine rng = makeSeededRandomEngine(iter, x, y); + thrust::uniform_real_distribution u01(0, 1); + + // calculate the ray origin + if (DOF) { + float aperture = 0.1; + float sampleX = u01(rng); + float sampleY = u01(rng); + + // warp pt to disk + float r = sqrt(sampleX); + float theta = 2 * 3.14159 * sampleY; + glm::vec2 res = glm::vec2(cos(theta), sin(theta)) * r; + + segment.ray.origin = cam.position + glm::vec3(res.x, res.y, 0) * aperture; + } + else { + segment.ray.origin = cam.position; + } + + if (ANTIALIASING) { + float rand1 = u01(rng); + float rand2 = u01(rng); + + x = x + rand1 * 2.0; + y = y + rand2 * 2.0; + } + + // calculate the ray direction + if (DOF) { + float focalLen = FOCAL_LEN; + float angle = glm::radians(cam.fov.y); + float aspect = ((float)cam.resolution.x / (float)cam.resolution.y); + float ndc_x = 1.f - ((float)x / cam.resolution.x) * 2.f; + float ndc_y = 1.f - ((float)y / cam.resolution.x) * 2.f; + + glm::vec3 ref = cam.position + cam.view * focalLen; + glm::vec3 H = tan(angle) * focalLen * cam.right * aspect; + glm::vec3 V = tan(angle) * focalLen * cam.up; + glm::vec3 target_pt = ref + V * ndc_y + H * ndc_x; + segment.ray.direction = normalize(target_pt - segment.ray.origin); + } + 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) + ); + + } + + segment.color = glm::vec3(1.0f, 1.0f, 1.0f); + segment.pixelIndex = index; + segment.remainingBounces = traceDepth; + segment.terminated = false; + } } +// TODO: +// computeIntersections handles generating ray intersections ONLY. +// Generating new rays is handled in your shader(s). +// Feel free to modify the code below. __global__ void computeIntersections( - int depth - , int num_paths - , PathSegment * pathSegments - , Geom * geoms - , int geoms_size - , ShadeableIntersection * intersections - ) + int depth + , int num_paths + , PathSegment** pathSegments + , Geom* geoms + , int geoms_size + , ShadeableIntersection* intersections + , Triangle* triangles +) { - int path_index = blockIdx.x * blockDim.x + threadIdx.x; - - if (path_index < num_paths) - { - PathSegment pathSegment = pathSegments[path_index]; - - float t; - glm::vec3 intersect_point; - glm::vec3 normal; - float t_min = FLT_MAX; - int hit_geom_index = -1; - bool outside = true; - - glm::vec3 tmp_intersect; - glm::vec3 tmp_normal; - - // naive parse through global geoms - - for (int i = 0; i < geoms_size; i++) - { - Geom & geom = geoms[i]; - - if (geom.type == CUBE) - { - t = boxIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); - } - else if (geom.type == SPHERE) - { - t = sphereIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); - } - - // 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; - hit_geom_index = i; - intersect_point = tmp_intersect; - normal = tmp_normal; - } - } - - if (hit_geom_index == -1) - { - intersections[path_index].t = -1.0f; - } - else - { - //The ray hits something - intersections[path_index].t = t_min; - intersections[path_index].materialId = geoms[hit_geom_index].materialid; - intersections[path_index].surfaceNormal = normal; - } - } + int path_index = blockIdx.x * blockDim.x + threadIdx.x; + + if (path_index < num_paths) + { + PathSegment pathSegment = *pathSegments[path_index]; + + float t; + glm::vec3 intersect_point; + glm::vec3 normal; + float t_min = FLT_MAX; + int hit_geom_index = -1; + bool outside = true; + + glm::vec3 tmp_intersect; + glm::vec3 tmp_normal; + + // naive parse through global geoms + + for (int i = 0; i < geoms_size; i++) + { + Geom& geom = geoms[i]; + + if (geom.type == CUBE) + { + t = boxIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); + } + else if (geom.type == SPHERE) + { + t = sphereIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside); + } + else if (geom.type == MESH) { + t = meshIntersectionTest(geom, pathSegment.ray, tmp_intersect, tmp_normal, outside, triangles); + } + // TODO: add more intersection tests here... triangle? metaball? CSG? + + // 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; + hit_geom_index = i; + intersect_point = tmp_intersect; + normal = tmp_normal; + } + } + + if (hit_geom_index == -1) + { + intersections[path_index].t = -1.0f; + //pathSegment.remainingBounces = 0; + } + else + { + //The ray hits something + intersections[path_index].t = t_min; + intersections[path_index].materialId = geoms[hit_geom_index].materialid; + intersections[path_index].surfaceNormal = normal; + } + } } -__global__ void shadeSimpleMaterials ( - int iter - , int num_paths - , ShadeableIntersection * shadeableIntersections - , PathSegment * pathSegments - , Material * materials - ) + +// LOOK: "fake" shader demonstrating what you might do with the info in +// a ShadeableIntersection, as well as how to use thrust's random number +// generator. Observe that since the thrust random number generator basically +// adds "noise" to the iteration, the image should start off noisy and get +// cleaner as more iterations are computed. +// +// Note that this shader does NOT do a BSDF evaluation! +// Your shaders should handle that - this can allow techniques such as +// bump mapping. +__global__ void shadeFakeMaterial( + int iter + , int num_paths + , ShadeableIntersection* shadeableIntersections + , PathSegment** pathSegments + , Material* materials +) { - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx < num_paths) - { - ShadeableIntersection intersection = shadeableIntersections[idx]; - PathSegment segment = pathSegments[idx]; - if (segment.remainingBounces == 0) { - return; + int idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < num_paths) + { + ShadeableIntersection intersection = shadeableIntersections[idx]; + if (intersection.t > 0.0f) { // if the intersection exists... + // Set up the RNG + thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, pathSegments[idx]->remainingBounces); + thrust::uniform_real_distribution u01(0, 1); + + Material material = materials[intersection.materialId]; + glm::vec3 materialColor = material.color; + + // If the material indicates that the object was a light, "light" the ray + if (material.emittance > 0.0f) { + pathSegments[idx]->color *= materialColor * material.emittance; + pathSegments[idx]->terminated = true; + } + else { + // multiply by the albedo color + pathSegments[idx]->color *= materialColor; + + // find and set next ray direction + glm::vec3 intersectPt = getPointOnRay(pathSegments[idx]->ray, intersection.t); + scatterRay(*pathSegments[idx], intersectPt, intersection.surfaceNormal, material, rng); + pathSegments[idx]->remainingBounces -= 1; + } + // If there was no intersection, color the ray black. + // Lots of renderers use 4 channel color, RGBA, where A = alpha, often + // used for opacity, in which case they can indicate "no opacity". + // This can be useful for post-processing and image compositing. + } + else { + pathSegments[idx]->color = glm::vec3(0.0f); + pathSegments[idx]->terminated = true; + } } +} - if (intersection.t > 0.0f) { // if the intersection exists... - segment.remainingBounces--; - // Set up the RNG - thrust::default_random_engine rng = makeSeededRandomEngine(iter, idx, segment.remainingBounces); - - Material material = materials[intersection.materialId]; - glm::vec3 materialColor = material.color; - - // If the material indicates that the object was a light, "light" the ray - if (material.emittance > 0.0f) { - segment.color *= (materialColor * material.emittance); - segment.remainingBounces = 0; - } - else { - segment.color *= materialColor; - glm::vec3 intersectPos = intersection.t * segment.ray.direction + segment.ray.origin; - scatterRay(segment, intersectPos, intersection.surfaceNormal, material, rng); - } - // If there was no intersection, color the ray black. - // Lots of renderers use 4 channel color, RGBA, where A = alpha, often - // used for opacity, in which case they can indicate "no opacity". - // This can be useful for post-processing and image compositing. - } else { - segment.color = glm::vec3(0.0f); - segment.remainingBounces = 0; - } +// Add the current iteration's output to the overall image +__global__ void finalGather(int nPaths, glm::vec3* image, PathSegment* iterationPaths) +{ + int index = (blockIdx.x * blockDim.x) + threadIdx.x; - pathSegments[idx] = segment; - } + if (index < nPaths) + { + PathSegment iterationPath = iterationPaths[index]; + image[iterationPath.pixelIndex] += iterationPath.color; + } } -__global__ void generateGBuffer ( - int num_paths, - ShadeableIntersection* shadeableIntersections, - PathSegment* pathSegments, - GBufferPixel* gBuffer) { - int idx = blockIdx.x * blockDim.x + threadIdx.x; - if (idx < num_paths) - { - gBuffer[idx].t = shadeableIntersections[idx].t; - } +__device__ float w(GBufferPixel& p, GBufferPixel& q, glm::vec3 colP, glm::vec3 colQ, float colW, float norW, float posW) { + float w_rt = min(exp(-dot(colP - colQ, colP - colQ) / colW), 1.0); + float w_n = exp(-dot(p.nor - q.nor, p.nor - q.nor) / norW); + float w_x = exp(-dot(p.pos - q.pos, p.pos - q.pos) / posW); + return w_rt * w_n * w_x; } -// Add the current iteration's output to the overall image -__global__ void finalGather(int nPaths, glm::vec3 * image, PathSegment * iterationPaths) +__global__ void applyATrousFilter(int nPaths, glm::vec3* dst, glm::vec3* prev_iter, glm::vec3* beauty, + float resolution, float* kernel, float offset, GBufferPixel* gbuffers, + float colW, float norW, float posW, int numIter, bool firstIter) { - int index = (blockIdx.x * blockDim.x) + threadIdx.x; + int index = (blockIdx.x * blockDim.x) + threadIdx.x; + + if (index < nPaths) + { + glm::vec3 cur_color = glm::vec3(0); + float k = 0; + + for (int x = -2; x < 3; ++x) { + for (int y = -2; y < 3; ++y) { + int newX = x * offset; + int newY = y * offset * resolution; + int l_index = index + newX + newY; + + if (!(l_index < 0 || l_index >= nPaths)) { + float h = kernel[(x + 2) + 5 * (y + 2)]; + glm::vec3 cq = firstIter ? beauty[l_index] / (float)numIter : dst[l_index]; + glm::vec3 cp = firstIter ? beauty[index] / (float)numIter : dst[index]; + float wW = w(gbuffers[index], gbuffers[l_index], cp, cq, colW, norW, posW); + cur_color += cq * h * wW; + k += h * wW; + } + + } + } + dst[index] += (cur_color / k) - prev_iter[index]; + prev_iter[index] = cur_color / k; + } +} + +void denoiseImage(float filterSize, float colW, float norW, float posW, int numIter) { + + std::clock_t start2; + double duration2; + start2 = std::clock(); + //std::cout << "starting clock at: " << start2 << std::endl; + + const Camera& cam = hst_scene->state.camera; + const int pixelcount = cam.resolution.x * cam.resolution.y; + const int blockSize1d = 128; + dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; + + cudaMemset(dev_denoised_tmp, 0, pixelcount * sizeof(glm::vec3)); + cudaMemset(dev_denoised, 0, pixelcount * sizeof(glm::vec3)); + + float iterations = filterSize < 5 ? 0 : floor(log2(filterSize / 5.f)); + float offset = 1; + + for (int i = 0; i < iterations; ++i) { + offset = pow(2, i); + if (i == 0) { + applyATrousFilter << > > (pixelcount, dev_denoised, dev_denoised_tmp, dev_image, cam.resolution.x, + dev_gaussian_kernel, offset, dev_gBuffer, colW, norW, posW, numIter, true); + } + else { + applyATrousFilter << > > (pixelcount, dev_denoised, dev_denoised_tmp, dev_image, cam.resolution.x, + dev_gaussian_kernel, offset, dev_gBuffer, colW, norW, posW, numIter, false); + } + } + + // duration2 = (std::clock() - start2) / (double)CLOCKS_PER_SEC; - if (index < nPaths) - { - PathSegment iterationPath = iterationPaths[index]; - image[iterationPath.pixelIndex] += iterationPath.color; - } + //cout.precision(17); + //std::cout << "ending clock at: " << std::clock() << std::endl; + //std::cout << "denoise time: " << fixed << duration2 << '\n'; } +// terminates ray if its terminated flag is raised +struct terminateRay { + __host__ __device__ bool operator()(const PathSegment* ps) { + return !ps->terminated; + } +}; + +// compares materials for sorting +struct compMaterialID : public binary_function { + __host__ __device__ bool operator()(const ShadeableIntersection& isect1, const ShadeableIntersection& isect2) { + return isect1.materialId < isect2.materialId; + } +}; + /** * Wrapper for the __global__ call that sets up the kernel calls and does a ton * of memory management */ -void pathtrace(int frame, int iter) { +void pathtrace(int frame, int iter, bool denoise) { const int traceDepth = hst_scene->state.traceDepth; - const Camera &cam = hst_scene->state.camera; + const Camera& cam = hst_scene->state.camera; const int pixelcount = cam.resolution.x * cam.resolution.y; + bool isFirstIter = iter == 1; - // 2D block for generating ray from camera + // 2D block for generating ray from camera const dim3 blockSize2d(8, 8); const dim3 blocksPerGrid2d( - (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, - (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); - - // 1D block for path tracing - const int blockSize1d = 128; - - /////////////////////////////////////////////////////////////////////////// - - // Pathtracing Recap: - // * Initialize array of path rays (using rays that come out of the camera) - // * You can pass the Camera object to that kernel. - // * Each path ray must carry at minimum a (ray, color) pair, - // * where color starts as the multiplicative identity, white = (1, 1, 1). - // * This has already been done for you. - // * NEW: For the first depth, generate geometry buffers (gbuffers) - // * For each depth: - // * Compute an intersection in the scene for each path ray. - // A very naive version of this has been implemented for you, but feel - // free to add more primitives and/or a better algorithm. - // Currently, intersection distance is recorded as a parametric distance, - // t, or a "distance along the ray." t = -1.0 indicates no intersection. - // * Color is attenuated (multiplied) by reflections off of any object - // * Stream compact away all of the terminated paths. - // You may use either your implementation or `thrust::remove_if` or its - // cousins. - // * Note that you can't really use a 2D kernel launch any more - switch - // to 1D. - // * Shade the rays that intersected something or didn't bottom out. - // That is, color the ray by performing a color computation according - // to the shader, then generate a new ray to continue the ray path. - // We recommend just updating the ray's PathSegment in place. - // Note that this step may come before or after stream compaction, - // since some shaders you write may also cause a path to terminate. - // * Finally: - // * if not denoising, add this iteration's results to the image - // * TODO: if denoising, run kernels that take both the raw pathtraced result and the gbuffer, and put the result in the "pbo" from opengl - - generateRayFromCamera <<>>(cam, iter, traceDepth, dev_paths); - checkCUDAError("generate camera ray"); - - int depth = 0; - PathSegment* dev_path_end = dev_paths + pixelcount; - int num_paths = dev_path_end - dev_paths; - - // --- PathSegment Tracing Stage --- - // Shoot ray into scene, bounce between objects, push shading chunks - - // Empty gbuffer - cudaMemset(dev_gBuffer, 0, pixelcount * sizeof(GBufferPixel)); - - // clean shading chunks - cudaMemset(dev_intersections, 0, pixelcount * sizeof(ShadeableIntersection)); - - bool iterationComplete = false; - while (!iterationComplete) { - - // 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(); - - if (depth == 0) { - generateGBuffer<<>>(num_paths, dev_intersections, dev_paths, dev_gBuffer); - } - - depth++; - - shadeSimpleMaterials<<>> ( - iter, - num_paths, - dev_intersections, - dev_paths, - dev_materials - ); - iterationComplete = depth == traceDepth; - } - - // Assemble this iteration and apply it to the image - dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; - finalGather<<>>(num_paths, dev_image, dev_paths); - - /////////////////////////////////////////////////////////////////////////// - - // CHECKITOUT: use dev_image as reference if you want to implement saving denoised images. - // Otherwise, screenshots are also acceptable. + (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, + (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); + + // 1D block for path tracing + const int blockSize1d = 128; + + generateRayFromCamera << > > (cam, iter, traceDepth, dev_paths, dev_alive_paths); + checkCUDAError("generate camera ray"); + + int depth = 0; + PathSegment* dev_path_end = dev_paths + pixelcount; + int init_num_paths = dev_path_end - dev_paths; + int num_paths = init_num_paths; + + bool iterationComplete = false; + thrust::device_ptr endPtr(dev_alive_paths + pixelcount); + + // if not the first iteration, assume the paths have been cached, harvest + if (CACHE_FIRST_BOUNCE && !ANTIALIASING && !DOF && !isFirstIter) { + cudaMemcpy(dev_paths, dev_first_paths, pixelcount * sizeof(PathSegment), cudaMemcpyDeviceToDevice); + depth++; // start on second bounce now + } + + 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_alive_paths + , dev_geoms + , hst_scene->geoms.size() + , dev_intersections + , dev_triangles + ); + checkCUDAError("trace one bounce"); + cudaDeviceSynchronize(); + + if (depth == 0) { + generateGBuffer << > > (num_paths, dev_intersections, dev_paths, dev_gBuffer); + } + + depth++; + + // sort rays by material + if (SORT_MATERIALS) { + thrust::device_ptr sorted_paths(dev_alive_paths); + thrust::device_ptr sorted_isects(dev_intersections); + thrust::sort_by_key(sorted_isects, sorted_isects + num_paths, sorted_paths, compMaterialID()); + } + + // shade paths + shadeFakeMaterial << > > ( + iter, + num_paths, + dev_intersections, + dev_alive_paths, + dev_materials + ); + + // if first iteration, cache first bounce + if (CACHE_FIRST_BOUNCE && !ANTIALIASING && !DOF && isFirstIter && depth == 1) { + cudaMemcpy(dev_first_paths, dev_paths, pixelcount * sizeof(PathSegment), cudaMemcpyDeviceToDevice); + } + + // perform stream compaction + thrust::device_ptr newPathsEnd = thrust::partition(dev_thrust_alive_paths, endPtr, terminateRay()); + endPtr = newPathsEnd; + num_paths = endPtr - dev_thrust_alive_paths; + + // if reached max depth or if no paths remain, terminate iteration + if (depth == traceDepth || num_paths == 0) { + iterationComplete = true; + } + } + + // Assemble this iteration and apply it to the image + dim3 numBlocksPixels = (pixelcount + blockSize1d - 1) / blockSize1d; + finalGather << > > (init_num_paths, dev_image, dev_paths); + // Retrieve image from GPU cudaMemcpy(hst_scene->state.image.data(), dev_image, - pixelcount * sizeof(glm::vec3), cudaMemcpyDeviceToHost); + pixelcount * sizeof(glm::vec3), cudaMemcpyDeviceToHost); checkCUDAError("pathtrace"); } // CHECKITOUT: this kernel "post-processes" the gbuffer/gbuffers into something that you can visualize for debugging. void showGBuffer(uchar4* pbo) { - const Camera &cam = hst_scene->state.camera; + const Camera& cam = hst_scene->state.camera; const dim3 blockSize2d(8, 8); const dim3 blocksPerGrid2d( - (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, - (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); + (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, + (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); // CHECKITOUT: process the gbuffer results and send them to OpenGL buffer for visualization - gbufferToPBO<<>>(pbo, cam.resolution, dev_gBuffer); + gbufferToPBO << > > (pbo, cam.resolution, dev_gBuffer); } -void showImage(uchar4* pbo, int iter) { -const Camera &cam = hst_scene->state.camera; +void showImage(uchar4* pbo, int iter, bool denoise) { + const Camera& cam = hst_scene->state.camera; const dim3 blockSize2d(8, 8); const dim3 blocksPerGrid2d( - (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, - (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); + (cam.resolution.x + blockSize2d.x - 1) / blockSize2d.x, + (cam.resolution.y + blockSize2d.y - 1) / blockSize2d.y); // Send results to OpenGL buffer for rendering - sendImageToPBO<<>>(pbo, cam.resolution, iter, dev_image); -} + if (denoise) { + sendDenoisedToPBO << > > (pbo, cam.resolution, iter, dev_denoised); + } + else { + sendImageToPBO << > > (pbo, cam.resolution, iter, dev_image); + } +} \ No newline at end of file diff --git a/src/pathtrace.h b/src/pathtrace.h index 9e12f44..1fa641c 100644 --- a/src/pathtrace.h +++ b/src/pathtrace.h @@ -5,6 +5,7 @@ void pathtraceInit(Scene *scene); void pathtraceFree(); -void pathtrace(int frame, int iteration); +void pathtrace(int frame, int iteration, bool denoise); void showGBuffer(uchar4 *pbo); -void showImage(uchar4 *pbo, int iter); +void showImage(uchar4 *pbo, int iter, bool denoise); +void denoiseImage(float filterSize, float colorW, float norW, float posW, int numIter); diff --git a/src/preview.cpp b/src/preview.cpp index 3ca2718..73bfc40 100644 --- a/src/preview.cpp +++ b/src/preview.cpp @@ -214,7 +214,7 @@ void drawGui(int windowWidth, int windowHeight) { ImGui::Checkbox("Denoise", &ui_denoise); - ImGui::SliderInt("Filter Size", &ui_filterSize, 0, 100); + ImGui::SliderInt("Filter Size", &ui_filterSize, 0, 640); ImGui::SliderFloat("Color Weight", &ui_colorWeight, 0.0f, 10.0f); ImGui::SliderFloat("Normal Weight", &ui_normalWeight, 0.0f, 10.0f); ImGui::SliderFloat("Position Weight", &ui_positionWeight, 0.0f, 10.0f); diff --git a/src/scene.cpp b/src/scene.cpp index cbae043..e570dc7 100644 --- a/src/scene.cpp +++ b/src/scene.cpp @@ -1,12 +1,21 @@ +#define TINYOBJLOADER_IMPLEMENTATION // define this in only one .cc #include #include "scene.h" #include #include #include +#include "tiny_obj_loader.h" + +#define USE_BB false Scene::Scene(string filename) { cout << "Reading scene from " << filename << " ..." << endl; cout << " " << endl; + + // initialize triangle ptr list + this->trianglePtrs = std::vector>>(); + //this->trianglePtrs = vector>>(); + char* fname = (char*)filename.c_str(); fp_in.open(fname); if (!fp_in.is_open()) { @@ -21,10 +30,12 @@ Scene::Scene(string filename) { if (strcmp(tokens[0].c_str(), "MATERIAL") == 0) { loadMaterial(tokens[1]); cout << " " << endl; - } else if (strcmp(tokens[0].c_str(), "OBJECT") == 0) { + } + else if (strcmp(tokens[0].c_str(), "OBJECT") == 0) { loadGeom(tokens[1]); cout << " " << endl; - } else if (strcmp(tokens[0].c_str(), "CAMERA") == 0) { + } + else if (strcmp(tokens[0].c_str(), "CAMERA") == 0) { loadCamera(); cout << " " << endl; } @@ -32,12 +43,122 @@ Scene::Scene(string filename) { } } +bool Scene::loadObj(string filename, Geom& geom) { + + // bounding box + geom.boundingBox.minX = 10000000.f; + geom.boundingBox.maxX = 0.f; + geom.boundingBox.minY = 10000000.f; + geom.boundingBox.maxY = 0.f; + geom.boundingBox.minZ = 10000000.f; + geom.boundingBox.maxZ = 0.f; + + std::vector triangles; + + // read in mesh and construct bounding box + tinyobj::ObjReaderConfig reader_config; + reader_config.triangulate = true; + tinyobj::ObjReader reader; + + // read from file + if (!reader.ParseFromFile(filename, reader_config)) { + if (!reader.Error().empty()) { + std::cerr << "TinyObjReader: " << reader.Error(); + } + exit(1); + } + + if (!reader.Warning().empty()) { + std::cout << "TinyObjReader: " << reader.Warning(); + } + + auto& attrib = reader.GetAttrib(); + auto& shapes = reader.GetShapes(); + + // loop over shapes + for (size_t s = 0; s < shapes.size(); s++) { + + // loop over faces + size_t index_offset = 0; + for (size_t f = 0; f < shapes[s].mesh.num_face_vertices.size(); f++) { + + Triangle t; + std::vector pts; + std::vector nors; + //loop over verts + for (size_t v = 0; v < 3; v++) { + 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]; + + // 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]; + + nors.push_back(glm::vec3(nx, ny, nz)); + } + + // check against bounding box bounds + geom.boundingBox.minX = min(geom.boundingBox.minX, vx); + geom.boundingBox.maxX = max(geom.boundingBox.maxX, vx); + geom.boundingBox.minY = min(geom.boundingBox.minY, vy); + geom.boundingBox.maxY = max(geom.boundingBox.maxY, vy); + geom.boundingBox.minZ = min(geom.boundingBox.minZ, vz); + geom.boundingBox.maxZ = max(geom.boundingBox.maxZ, vz); + + pts.push_back(glm::vec3(vx, vy, vz)); + } + index_offset += 3; + t.p1 = glm::vec3(pts[0].x, pts[0].y, pts[0].z); + t.p2 = glm::vec3(pts[1].x, pts[1].y, pts[1].z); + t.p3 = glm::vec3(pts[2].x, pts[2].y, pts[2].z); + t.n1 = glm::vec3(nors[0].x, nors[0].y, nors[0].z); + t.n2 = glm::vec3(nors[1].x, nors[1].y, nors[1].z); + t.n3 = glm::vec3(nors[2].x, nors[2].y, nors[2].z); + triangles.push_back(t); + } + } + + // save unique ptr to triangle vector in scene + this->trianglePtrs.push_back(make_unique>(triangles)); + + // get raw ptr and save to geom + geom.triangles = &this->trianglePtrs[this->trianglePtrs.size() - 1].get()->front(); + geom.numTriangles = triangles.size(); + + return true; +} + +void calcBoundingBox(Geom& geom) { + // calc scale of bounding box in mesh's untransformed space + glm::vec3 bbScale(geom.boundingBox.maxX - geom.boundingBox.minX, + geom.boundingBox.maxY - geom.boundingBox.minY, + geom.boundingBox.maxZ - geom.boundingBox.minZ); + // bb scale assumes we are scaling uniformly -- translate so that it's placed correctly + glm::vec3 unitBox(0.5, 0.5, 0.5); + glm::vec3 bbTop(geom.boundingBox.maxX, geom.boundingBox.maxY, geom.boundingBox.maxZ); + glm::vec3 bbTrans = bbTop - unitBox * bbScale; + + // translate/scale resulting bounding box + bbScale *= geom.scale; + bbTrans += geom.translation; + + geom.boundingBox.transform = utilityCore::buildTransformationMatrix( + bbTrans, geom.rotation, bbScale); + geom.boundingBox.inverseTransform = glm::inverse(geom.boundingBox.transform); + geom.boundingBox.invTranspose = glm::inverseTranspose(geom.boundingBox.transform); +} + int Scene::loadGeom(string objectid) { int id = atoi(objectid.c_str()); if (id != geoms.size()) { cout << "ERROR: OBJECT ID does not match expected number of geoms" << endl; return -1; - } else { + } + else { cout << "Loading Geom " << id << "..." << endl; Geom newGeom; string line; @@ -48,10 +169,15 @@ int Scene::loadGeom(string objectid) { if (strcmp(line.c_str(), "sphere") == 0) { cout << "Creating new sphere..." << endl; newGeom.type = SPHERE; - } else if (strcmp(line.c_str(), "cube") == 0) { + } + else if (strcmp(line.c_str(), "cube") == 0) { cout << "Creating new cube..." << endl; newGeom.type = CUBE; } + else if (strcmp(line.c_str(), "mesh") == 0) { + cout << "Creating new mesh..." << endl; + newGeom.type = MESH; + } } //link material @@ -62,6 +188,21 @@ int Scene::loadGeom(string objectid) { cout << "Connecting Geom " << objectid << " to Material " << newGeom.materialid << "..." << endl; } + //link filename for obj + if (newGeom.type == MESH) { + utilityCore::safeGetline(fp_in, line); + if (!line.empty() && fp_in.good()) { + vector tokens = utilityCore::tokenizeString(line); + + if (strcmp(tokens[0].c_str(), "FILENAME") == 0) { + string filename = tokens[1].c_str(); + std::cout << "Reading obj file from " << filename << " ..." << endl; + + if (!loadObj(filename, newGeom)) return -1; + } + } + } + //load transformations utilityCore::safeGetline(fp_in, line); while (!line.empty() && fp_in.good()) { @@ -70,9 +211,11 @@ int Scene::loadGeom(string objectid) { //load tranformations if (strcmp(tokens[0].c_str(), "TRANS") == 0) { newGeom.translation = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } else if (strcmp(tokens[0].c_str(), "ROTAT") == 0) { + } + else if (strcmp(tokens[0].c_str(), "ROTAT") == 0) { newGeom.rotation = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } else if (strcmp(tokens[0].c_str(), "SCALE") == 0) { + } + else if (strcmp(tokens[0].c_str(), "SCALE") == 0) { newGeom.scale = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); } @@ -80,19 +223,27 @@ int Scene::loadGeom(string objectid) { } newGeom.transform = utilityCore::buildTransformationMatrix( - newGeom.translation, newGeom.rotation, newGeom.scale); + newGeom.translation, newGeom.rotation, newGeom.scale); newGeom.inverseTransform = glm::inverse(newGeom.transform); newGeom.invTranspose = glm::inverseTranspose(newGeom.transform); + // if mesh, set bounding box transformations + if (newGeom.type == MESH && USE_BB) { + calcBoundingBox(newGeom); + } + + //if (newGeom.type != MESH) { geoms.push_back(newGeom); + //} + return 1; } } int Scene::loadCamera() { cout << "Loading Camera ..." << endl; - RenderState &state = this->state; - Camera &camera = state.camera; + RenderState& state = this->state; + Camera& camera = state.camera; float fovy; //load static properties @@ -103,13 +254,17 @@ int Scene::loadCamera() { if (strcmp(tokens[0].c_str(), "RES") == 0) { camera.resolution.x = atoi(tokens[1].c_str()); camera.resolution.y = atoi(tokens[2].c_str()); - } else if (strcmp(tokens[0].c_str(), "FOVY") == 0) { + } + else if (strcmp(tokens[0].c_str(), "FOVY") == 0) { fovy = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "ITERATIONS") == 0) { + } + else if (strcmp(tokens[0].c_str(), "ITERATIONS") == 0) { state.iterations = atoi(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "DEPTH") == 0) { + } + else if (strcmp(tokens[0].c_str(), "DEPTH") == 0) { state.traceDepth = atoi(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "FILE") == 0) { + } + else if (strcmp(tokens[0].c_str(), "FILE") == 0) { state.imageName = tokens[1]; } } @@ -120,9 +275,11 @@ int Scene::loadCamera() { vector tokens = utilityCore::tokenizeString(line); if (strcmp(tokens[0].c_str(), "EYE") == 0) { camera.position = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } else if (strcmp(tokens[0].c_str(), "LOOKAT") == 0) { + } + else if (strcmp(tokens[0].c_str(), "LOOKAT") == 0) { camera.lookAt = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); - } else if (strcmp(tokens[0].c_str(), "UP") == 0) { + } + else if (strcmp(tokens[0].c_str(), "UP") == 0) { camera.up = glm::vec3(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); } @@ -135,9 +292,9 @@ int Scene::loadCamera() { float fovx = (atan(xscaled) * 180) / PI; camera.fov = glm::vec2(fovx, fovy); - camera.right = glm::normalize(glm::cross(camera.view, camera.up)); - camera.pixelLength = glm::vec2(2 * xscaled / (float)camera.resolution.x - , 2 * yscaled / (float)camera.resolution.y); + camera.right = glm::normalize(glm::cross(camera.view, camera.up)); + camera.pixelLength = glm::vec2(2 * xscaled / (float)camera.resolution.x, + 2 * yscaled / (float)camera.resolution.y); camera.view = glm::normalize(camera.lookAt - camera.position); @@ -155,7 +312,8 @@ int Scene::loadMaterial(string materialid) { if (id != materials.size()) { cout << "ERROR: MATERIAL ID does not match expected number of materials" << endl; return -1; - } else { + } + else { cout << "Loading Material " << id << "..." << endl; Material newMaterial; @@ -165,24 +323,30 @@ int Scene::loadMaterial(string materialid) { utilityCore::safeGetline(fp_in, line); vector tokens = utilityCore::tokenizeString(line); if (strcmp(tokens[0].c_str(), "RGB") == 0) { - glm::vec3 color( atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str()) ); + glm::vec3 color(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); newMaterial.color = color; - } else if (strcmp(tokens[0].c_str(), "SPECEX") == 0) { + } + else if (strcmp(tokens[0].c_str(), "SPECEX") == 0) { newMaterial.specular.exponent = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "SPECRGB") == 0) { + } + else if (strcmp(tokens[0].c_str(), "SPECRGB") == 0) { glm::vec3 specColor(atof(tokens[1].c_str()), atof(tokens[2].c_str()), atof(tokens[3].c_str())); newMaterial.specular.color = specColor; - } else if (strcmp(tokens[0].c_str(), "REFL") == 0) { + } + else if (strcmp(tokens[0].c_str(), "REFL") == 0) { newMaterial.hasReflective = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "REFR") == 0) { + } + else if (strcmp(tokens[0].c_str(), "REFR") == 0) { newMaterial.hasRefractive = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "REFRIOR") == 0) { + } + else if (strcmp(tokens[0].c_str(), "REFRIOR") == 0) { newMaterial.indexOfRefraction = atof(tokens[1].c_str()); - } else if (strcmp(tokens[0].c_str(), "EMITTANCE") == 0) { + } + else if (strcmp(tokens[0].c_str(), "EMITTANCE") == 0) { newMaterial.emittance = atof(tokens[1].c_str()); } } materials.push_back(newMaterial); return 1; } -} +} \ No newline at end of file diff --git a/src/scene.h b/src/scene.h index f29a917..3969e96 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(); + bool loadObj(string filename, Geom& geom); public: Scene(string filename); ~Scene(); std::vector geoms; std::vector materials; + std::vector>> trianglePtrs; RenderState state; }; diff --git a/src/sceneStructs.h b/src/sceneStructs.h index da7e558..28ed8d0 100644 --- a/src/sceneStructs.h +++ b/src/sceneStructs.h @@ -10,6 +10,7 @@ enum GeomType { SPHERE, CUBE, + MESH }; struct Ray { @@ -17,8 +18,28 @@ struct Ray { glm::vec3 direction; }; +struct Triangle { + glm::vec3 p1; + glm::vec3 p2; + glm::vec3 p3; + glm::vec3 n1; + glm::vec3 n2; + glm::vec3 n3; +}; + struct Geom { enum GeomType type; + struct { + float minX; + float minY; + float minZ; + float maxX; + float maxY; + float maxZ; + glm::mat4 transform; + glm::mat4 inverseTransform; + glm::mat4 invTranspose; + } boundingBox; int materialid; glm::vec3 translation; glm::vec3 rotation; @@ -26,6 +47,8 @@ struct Geom { glm::mat4 transform; glm::mat4 inverseTransform; glm::mat4 invTranspose; + Triangle* triangles; + int numTriangles; }; struct Material { @@ -64,6 +87,7 @@ struct PathSegment { glm::vec3 color; int pixelIndex; int remainingBounces; + bool terminated; }; // Use with a corresponding PathSegment to do: @@ -79,4 +103,6 @@ struct ShadeableIntersection { // What information might be helpful for guiding a denoising filter? struct GBufferPixel { float t; + glm::vec3 pos; + glm::vec3 nor; }; diff --git a/src/tiny_obj_loader.h b/src/tiny_obj_loader.h new file mode 100644 index 0000000..5e0c2df --- /dev/null +++ b/src/tiny_obj_loader.h @@ -0,0 +1,3366 @@ + +/* +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 + +#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::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++) { + 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; + 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::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++) { + 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; + 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 \ No newline at end of file