From bf68e43af81aac8aba63c468afd751a6df025f3b Mon Sep 17 00:00:00 2001 From: redstrate <54911369+redstrate@users.noreply.github.com> Date: Mon, 17 Feb 2020 10:33:56 -0500 Subject: [PATCH] Add initial files --- .gitignore | 72 + CMakeLists.txt | 23 + LICENSE | 21 + README.md | 5 + extern/CMakeLists.txt | 1 + extern/stb/CMakeLists.txt | 2 + extern/stb/include/stb_image_write.h | 1092 ++++++++++++++ include/camera.h | 28 + include/image.h | 17 + include/intersections.h | 64 + include/lighting.h | 12 + include/ray.h | 9 + include/scene.h | 93 ++ include/tiny_obj_loader.h | 2029 ++++++++++++++++++++++++++ misc/output.png | Bin 0 -> 23617 bytes src/main.cpp | 99 ++ 16 files changed, 3567 insertions(+) create mode 100644 .gitignore create mode 100644 CMakeLists.txt create mode 100644 LICENSE create mode 100644 README.md create mode 100644 extern/CMakeLists.txt create mode 100644 extern/stb/CMakeLists.txt create mode 100644 extern/stb/include/stb_image_write.h create mode 100644 include/camera.h create mode 100644 include/image.h create mode 100644 include/intersections.h create mode 100644 include/lighting.h create mode 100644 include/ray.h create mode 100644 include/scene.h create mode 100644 include/tiny_obj_loader.h create mode 100644 misc/output.png create mode 100644 src/main.cpp diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1f1481b --- /dev/null +++ b/.gitignore @@ -0,0 +1,72 @@ +build/ +*build/ + +.DS_Store + +CMakeLists.txt.user +CMakeCache.txt +CMakeFiles +CMakeScripts +Testing +Makefile +cmake_install.cmake +install_manifest.txt +compile_commands.json +CTestTestfile.cmake +_deps + +xcuserdata/ + +## compatibility with Xcode 8 and earlier (ignoring not required starting Xcode 9) +*.xcscmblueprint +*.xccheckout + +## compatibility with Xcode 3 and earlier (ignoring not required starting Xcode 4) +build/ +DerivedData/ +*.moved-aside +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 + +## Gcc Patch +/*.gcno + + +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..bc9854b --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,23 @@ +cmake_minimum_required(VERSION 2.6) +project(raytracer) + +find_package(GLM REQUIRED) + +add_subdirectory(extern) + +add_executable(raytracer + include/camera.h + include/intersections.h + include/lighting.h + include/ray.h + include/image.h + include/tiny_obj_loader.h + include/scene.h + src/main.cpp) +target_include_directories(raytracer PUBLIC include) +target_link_libraries(raytracer PRIVATE stb glm) +set_target_properties(raytracer PROPERTIES + CXX_STANDARD 17 + CXX_STANDARD_REQUIRED YES + CXX_EXTENSIONS NO +) diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..bdecdd0 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020 Joshua Goins + +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. \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..b3d083b --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# Raytracer + +A multi-threaded raytracer using glm, tinyobjloader, stb and C++17. + +![example result](https://raw.githubusercontent.com/redstrate/raytracer/master/misc/output.png) diff --git a/extern/CMakeLists.txt b/extern/CMakeLists.txt new file mode 100644 index 0000000..6c3f5c8 --- /dev/null +++ b/extern/CMakeLists.txt @@ -0,0 +1 @@ +add_subdirectory(stb) diff --git a/extern/stb/CMakeLists.txt b/extern/stb/CMakeLists.txt new file mode 100644 index 0000000..1897094 --- /dev/null +++ b/extern/stb/CMakeLists.txt @@ -0,0 +1,2 @@ +add_library(stb INTERFACE) +target_include_directories(stb INTERFACE include) diff --git a/extern/stb/include/stb_image_write.h b/extern/stb/include/stb_image_write.h new file mode 100644 index 0000000..6938722 --- /dev/null +++ b/extern/stb/include/stb_image_write.h @@ -0,0 +1,1092 @@ +/* stb_image_write - v1.05 - public domain - http://nothings.org/stb/stb_image_write.h +writes out PNG/BMP/TGA images to C stdio - Sean Barrett 2010-2015 +no warranty implied; use at your own risk + +Before #including, + +#define STB_IMAGE_WRITE_IMPLEMENTATION + +in the file that you want to have the implementation. + +Will probably not work correctly with strict-aliasing optimizations. + +ABOUT: + +This header file is a library for writing images to C stdio. It could be +adapted to write to memory or a general streaming interface; let me know. + +The PNG output is not optimal; it is 20-50% larger than the file +written by a decent optimizing implementation. This library is designed +for source code compactness and simplicity, not optimal image file size +or run-time performance. + +BUILDING: + +You can #define STBIW_ASSERT(x) before the #include to avoid using assert.h. +You can #define STBIW_MALLOC(), STBIW_REALLOC(), and STBIW_FREE() to replace +malloc,realloc,free. +You can define STBIW_MEMMOVE() to replace memmove() + +USAGE: + +There are four functions, one for each image file format: + +int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); +int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); +int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); +int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); + +There are also four equivalent functions that use an arbitrary write function. You are +expected to open/close your file-equivalent before and after calling these: + +int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); +int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); +int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + +where the callback is: +void stbi_write_func(void *context, void *data, int size); + +You can define STBI_WRITE_NO_STDIO to disable the file variant of these +functions, so the library will not use stdio.h at all. However, this will +also disable HDR writing, because it requires stdio for formatted output. + +Each function returns 0 on failure and non-0 on success. + +The functions create an image file defined by the parameters. The image +is a rectangle of pixels stored from left-to-right, top-to-bottom. +Each pixel contains 'comp' channels of data stored interleaved with 8-bits +per channel, in the following order: 1=Y, 2=YA, 3=RGB, 4=RGBA. (Y is +monochrome color.) The rectangle is 'w' pixels wide and 'h' pixels tall. +The *data pointer points to the first byte of the top-left-most pixel. +For PNG, "stride_in_bytes" is the distance in bytes from the first byte of +a row of pixels to the first byte of the next row of pixels. + +PNG creates output files with the same number of components as the input. +The BMP format expands Y to RGB in the file format and does not +output alpha. + +PNG supports writing rectangles of data even when the bytes storing rows of +data are not consecutive in memory (e.g. sub-rectangles of a larger image), +by supplying the stride between the beginning of adjacent rows. The other +formats do not. (Thus you cannot write a native-format BMP through the BMP +writer, both because it is in BGR order and because it may have padding +at the end of the line.) + +HDR expects linear float data. Since the format is always 32-bit rgb(e) +data, alpha (if provided) is discarded, and for monochrome data it is +replicated across all three channels. + +TGA supports RLE or non-RLE compressed data. To use non-RLE-compressed +data, set the global variable 'stbi_write_tga_with_rle' to 0. + +CREDITS: + +PNG/BMP/TGA +Sean Barrett +HDR +Baldur Karlsson +TGA monochrome: +Jean-Sebastien Guay +misc enhancements: +Tim Kelsey +TGA RLE +Alan Hickman +initial file IO callback implementation +Emmanuel Julien +bugfixes: +github:Chribba +Guillaume Chereau +github:jry2 +github:romigrou +Sergio Gonzalez +Jonas Karlsson +Filip Wasil +Thatcher Ulrich +github:poppolopoppo +Patrick Boettcher + +LICENSE + +See end of file for license information. + + */ + +#ifndef INCLUDE_STB_IMAGE_WRITE_H +# define INCLUDE_STB_IMAGE_WRITE_H + +# ifdef __cplusplus +extern "C" { +# endif + +# ifdef STB_IMAGE_WRITE_STATIC +# define STBIWDEF static +# else +# define STBIWDEF extern + extern int stbi_write_tga_with_rle; +# endif + +# ifndef STBI_WRITE_NO_STDIO + STBIWDEF int stbi_write_png(char const *filename, int w, int h, int comp, const void *data, int stride_in_bytes); + STBIWDEF int stbi_write_bmp(char const *filename, int w, int h, int comp, const void *data); + STBIWDEF int stbi_write_tga(char const *filename, int w, int h, int comp, const void *data); + STBIWDEF int stbi_write_hdr(char const *filename, int w, int h, int comp, const float *data); +# endif + + typedef void stbi_write_func(void *context, void *data, int size); + + STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data, int stride_in_bytes); + STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + STBIWDEF int stbi_write_tga_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const void *data); + STBIWDEF int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int w, int h, int comp, const float *data); + +# ifdef __cplusplus + } +# endif + +#endif //INCLUDE_STB_IMAGE_WRITE_H + +#ifdef STB_IMAGE_WRITE_IMPLEMENTATION + +# ifdef _WIN32 +# ifndef _CRT_SECURE_NO_WARNINGS +# define _CRT_SECURE_NO_WARNINGS +# endif +# ifndef _CRT_NONSTDC_NO_DEPRECATE +# define _CRT_NONSTDC_NO_DEPRECATE +# endif +# endif + +# ifndef STBI_WRITE_NO_STDIO +# include +# endif // STBI_WRITE_NO_STDIO + +# include +# include +# include +# include + +# if defined(STBIW_MALLOC) && defined(STBIW_FREE) && (defined(STBIW_REALLOC) || defined(STBIW_REALLOC_SIZED)) +// ok +# elif !defined(STBIW_MALLOC) && !defined(STBIW_FREE) && !defined(STBIW_REALLOC) && !defined(STBIW_REALLOC_SIZED) +// ok +# else +# error "Must define all or none of STBIW_MALLOC, STBIW_FREE, and STBIW_REALLOC (or STBIW_REALLOC_SIZED)." +# endif + +# ifndef STBIW_MALLOC +# define STBIW_MALLOC(sz) malloc(sz) +# define STBIW_REALLOC(p,newsz) realloc(p,newsz) +# define STBIW_FREE(p) free(p) +# endif + +# ifndef STBIW_REALLOC_SIZED +# define STBIW_REALLOC_SIZED(p,oldsz,newsz) STBIW_REALLOC(p,newsz) +# endif + + +# ifndef STBIW_MEMMOVE +# define STBIW_MEMMOVE(a,b,sz) memmove(a,b,sz) +# endif + + +# ifndef STBIW_ASSERT +# include +# define STBIW_ASSERT(x) assert(x) +# endif + +# define STBIW_UCHAR(x) (unsigned char) ((x) & 0xff) + +typedef struct +{ + stbi_write_func *func; + void *context; +} stbi__write_context; + +// initialize a callback-based context +static void stbi__start_write_callbacks(stbi__write_context *s, stbi_write_func *c, void *context) +{ + s->func = c; + s->context = context; +} + +# ifndef STBI_WRITE_NO_STDIO + +static void stbi__stdio_write(void *context, void *data, int size) +{ + fwrite(data,1,size,(FILE*) context); +} + +static int stbi__start_write_file(stbi__write_context *s, const char *filename) +{ + FILE *f = fopen(filename, "wb"); + stbi__start_write_callbacks(s, stbi__stdio_write, (void *) f); + return f != NULL; +} + +static void stbi__end_write_file(stbi__write_context *s) +{ + fclose((FILE *)s->context); +} + +# endif // !STBI_WRITE_NO_STDIO + +typedef unsigned int stbiw_uint32; +typedef int stb_image_write_test[sizeof(stbiw_uint32)==4 ? 1 : -1]; + +# ifdef STB_IMAGE_WRITE_STATIC +static int stbi_write_tga_with_rle = 1; +# else +int stbi_write_tga_with_rle = 1; +# endif + +static void stbiw__writefv(stbi__write_context *s, const char *fmt, va_list v) +{ + while (*fmt) { + switch (*fmt++) { + case ' ': break; + case '1': { unsigned char x = STBIW_UCHAR(va_arg(v, int)); + s->func(s->context,&x,1); + break; } + case '2': { int x = va_arg(v,int); + unsigned char b[2]; + b[0] = STBIW_UCHAR(x); + b[1] = STBIW_UCHAR(x>>8); + s->func(s->context,b,2); + break; } + case '4': { stbiw_uint32 x = va_arg(v,int); + unsigned char b[4]; + b[0]=STBIW_UCHAR(x); + b[1]=STBIW_UCHAR(x>>8); + b[2]=STBIW_UCHAR(x>>16); + b[3]=STBIW_UCHAR(x>>24); + s->func(s->context,b,4); + break; } + default: + STBIW_ASSERT(0); + return; + } + } +} + +static void stbiw__writef(stbi__write_context *s, const char *fmt, ...) +{ + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); +} + +static void stbiw__write3(stbi__write_context *s, unsigned char a, unsigned char b, unsigned char c) +{ + unsigned char arr[3]; + arr[0] = a, arr[1] = b, arr[2] = c; + s->func(s->context, arr, 3); +} + +static void stbiw__write_pixel(stbi__write_context *s, int rgb_dir, int comp, int write_alpha, int expand_mono, unsigned char *d) +{ + unsigned char bg[3] = { 255, 0, 255}, px[3]; + int k; + + if (write_alpha < 0) + s->func(s->context, &d[comp - 1], 1); + + switch (comp) { + case 2: // 2 pixels = mono + alpha, alpha is written separately, so same as 1-channel case + case 1: + if (expand_mono) + stbiw__write3(s, d[0], d[0], d[0]); // monochrome bmp + else + s->func(s->context, d, 1); // monochrome TGA + break; + case 4: + if (!write_alpha) { + // composite against pink background + for (k = 0; k < 3; ++k) + px[k] = bg[k] + ((d[k] - bg[k]) * d[3]) / 255; + stbiw__write3(s, px[1 - rgb_dir], px[1], px[1 + rgb_dir]); + break; + } + /* FALLTHROUGH */ + case 3: + stbiw__write3(s, d[1 - rgb_dir], d[1], d[1 + rgb_dir]); + break; + } + if (write_alpha > 0) + s->func(s->context, &d[comp - 1], 1); +} + +static void stbiw__write_pixels(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, void *data, int write_alpha, int scanline_pad, int expand_mono) +{ + stbiw_uint32 zero = 0; + int i,j, j_end; + + if (y <= 0) + return; + + if (vdir < 0) + j_end = -1, j = y-1; + else + j_end = y, j = 0; + + for (; j != j_end; j += vdir) { + for (i=0; i < x; ++i) { + unsigned char *d = (unsigned char *) data + (j*x+i)*comp; + stbiw__write_pixel(s, rgb_dir, comp, write_alpha, expand_mono, d); + } + s->func(s->context, &zero, scanline_pad); + } +} + +static int stbiw__outfile(stbi__write_context *s, int rgb_dir, int vdir, int x, int y, int comp, int expand_mono, void *data, int alpha, int pad, const char *fmt, ...) +{ + if (y < 0 || x < 0) { + return 0; + } else { + va_list v; + va_start(v, fmt); + stbiw__writefv(s, fmt, v); + va_end(v); + stbiw__write_pixels(s,rgb_dir,vdir,x,y,comp,data,alpha,pad, expand_mono); + return 1; + } +} + +static int stbi_write_bmp_core(stbi__write_context *s, int x, int y, int comp, const void *data) +{ + int pad = (-x*3) & 3; + return stbiw__outfile(s,-1,-1,x,y,comp,1,(void *) data,0,pad, + "11 4 22 4" "4 44 22 444444", + 'B', 'M', 14+40+(x*3+pad)*y, 0,0, 14+40, // file header + 40, x,y, 1,24, 0,0,0,0,0,0); // bitmap header +} + +STBIWDEF int stbi_write_bmp_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_bmp_core(&s, x, y, comp, data); +} + +# ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_bmp(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_bmp_core(&s, x, y, comp, data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +# endif //!STBI_WRITE_NO_STDIO + +static int stbi_write_tga_core(stbi__write_context *s, int x, int y, int comp, void *data) +{ + int has_alpha = (comp == 2 || comp == 4); + int colorbytes = has_alpha ? comp-1 : comp; + int format = colorbytes < 2 ? 3 : 2; // 3 color channels (RGB/RGBA) = 2, 1 color channel (Y/YA) = 3 + + if (y < 0 || x < 0) + return 0; + + if (!stbi_write_tga_with_rle) { + return stbiw__outfile(s, -1, -1, x, y, comp, 0, (void *) data, has_alpha, 0, + "111 221 2222 11", 0, 0, format, 0, 0, 0, 0, 0, x, y, (colorbytes + has_alpha) * 8, has_alpha * 8); + } else { + int i,j,k; + + stbiw__writef(s, "111 221 2222 11", 0,0,format+8, 0,0,0, 0,0,x,y, (colorbytes + has_alpha) * 8, has_alpha * 8); + + for (j = y - 1; j >= 0; --j) { + unsigned char *row = (unsigned char *) data + j * x * comp; + int len; + + for (i = 0; i < x; i += len) { + unsigned char *begin = row + i * comp; + int diff = 1; + len = 1; + + if (i < x - 1) { + ++len; + diff = memcmp(begin, row + (i + 1) * comp, comp); + if (diff) { + const unsigned char *prev = begin; + for (k = i + 2; k < x && len < 128; ++k) { + if (memcmp(prev, row + k * comp, comp)) { + prev += comp; + ++len; + } else { + --len; + break; + } + } + } else { + for (k = i + 2; k < x && len < 128; ++k) { + if (!memcmp(begin, row + k * comp, comp)) { + ++len; + } else { + break; + } + } + } + } + + if (diff) { + unsigned char header = STBIW_UCHAR(len - 1); + s->func(s->context, &header, 1); + for (k = 0; k < len; ++k) { + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin + k * comp); + } + } else { + unsigned char header = STBIW_UCHAR(len - 129); + s->func(s->context, &header, 1); + stbiw__write_pixel(s, -1, comp, has_alpha, 0, begin); + } + } + } + } + return 1; +} + +int stbi_write_tga_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_tga_core(&s, x, y, comp, (void *) data); +} + +# ifndef STBI_WRITE_NO_STDIO +int stbi_write_tga(char const *filename, int x, int y, int comp, const void *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_tga_core(&s, x, y, comp, (void *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +# endif + +// ************************************************************************************************* +// Radiance RGBE HDR writer +// by Baldur Karlsson + +# define stbiw__max(a, b) ((a) > (b) ? (a) : (b)) + +void stbiw__linear_to_rgbe(unsigned char *rgbe, float *linear) +{ + int exponent; + float maxcomp = stbiw__max(linear[0], stbiw__max(linear[1], linear[2])); + + if (maxcomp < 1e-32f) { + rgbe[0] = rgbe[1] = rgbe[2] = rgbe[3] = 0; + } else { + float normalize = (float) frexp(maxcomp, &exponent) * 256.0f/maxcomp; + + rgbe[0] = (unsigned char)(linear[0] * normalize); + rgbe[1] = (unsigned char)(linear[1] * normalize); + rgbe[2] = (unsigned char)(linear[2] * normalize); + rgbe[3] = (unsigned char)(exponent + 128); + } +} + +void stbiw__write_run_data(stbi__write_context *s, int length, unsigned char databyte) +{ + unsigned char lengthbyte = STBIW_UCHAR(length+128); + STBIW_ASSERT(length+128 <= 255); + s->func(s->context, &lengthbyte, 1); + s->func(s->context, &databyte, 1); +} + +void stbiw__write_dump_data(stbi__write_context *s, int length, unsigned char *data) +{ + unsigned char lengthbyte = STBIW_UCHAR(length); + STBIW_ASSERT(length <= 128); // inconsistent with spec but consistent with official code + s->func(s->context, &lengthbyte, 1); + s->func(s->context, data, length); +} + +void stbiw__write_hdr_scanline(stbi__write_context *s, int width, int ncomp, unsigned char *scratch, float *scanline) +{ + unsigned char scanlineheader[4] = { 2, 2, 0, 0 }; + unsigned char rgbe[4]; + float linear[3]; + int x; + + scanlineheader[2] = (width&0xff00)>>8; + scanlineheader[3] = (width&0x00ff); + + /* skip RLE for images too small or large */ + if (width < 8 || width >= 32768) { + for (x=0; x < width; x++) { + switch (ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + s->func(s->context, rgbe, 4); + } + } else { + int c,r; + /* encode into scratch buffer */ + for (x=0; x < width; x++) { + switch(ncomp) { + case 4: /* fallthrough */ + case 3: linear[2] = scanline[x*ncomp + 2]; + linear[1] = scanline[x*ncomp + 1]; + linear[0] = scanline[x*ncomp + 0]; + break; + default: + linear[0] = linear[1] = linear[2] = scanline[x*ncomp + 0]; + break; + } + stbiw__linear_to_rgbe(rgbe, linear); + scratch[x + width*0] = rgbe[0]; + scratch[x + width*1] = rgbe[1]; + scratch[x + width*2] = rgbe[2]; + scratch[x + width*3] = rgbe[3]; + } + + s->func(s->context, scanlineheader, 4); + + /* RLE each component separately */ + for (c=0; c < 4; c++) { + unsigned char *comp = &scratch[width*c]; + + x = 0; + while (x < width) { + // find first run + r = x; + while (r+2 < width) { + if (comp[r] == comp[r+1] && comp[r] == comp[r+2]) + break; + ++r; + } + if (r+2 >= width) + r = width; + // dump up to first run + while (x < r) { + int len = r-x; + if (len > 128) len = 128; + stbiw__write_dump_data(s, len, &comp[x]); + x += len; + } + // if there's a run, output it + if (r+2 < width) { // same test as what we break out of in search loop, so only true if we break'd + // find next byte after run + while (r < width && comp[r] == comp[x]) + ++r; + // output run up to r + while (x < r) { + int len = r-x; + if (len > 127) len = 127; + stbiw__write_run_data(s, len, comp[x]); + x += len; + } + } + } + } + } +} + +static int stbi_write_hdr_core(stbi__write_context *s, int x, int y, int comp, float *data) +{ + if (y <= 0 || x <= 0 || data == NULL) + return 0; + else { + // Each component is stored separately. Allocate scratch space for full output scanline. + unsigned char *scratch = (unsigned char *) STBIW_MALLOC(x*4); + int i, len; + char buffer[128]; + char header[] = "#?RADIANCE\n# Written by stb_image_write.h\nFORMAT=32-bit_rle_rgbe\n"; + s->func(s->context, header, sizeof(header)-1); + + len = sprintf(buffer, "EXPOSURE= 1.0000000000000\n\n-Y %d +X %d\n", y, x); + s->func(s->context, buffer, len); + + for(i=0; i < y; i++) + stbiw__write_hdr_scanline(s, x, comp, scratch, data + comp*i*x); + STBIW_FREE(scratch); + return 1; + } +} + +int stbi_write_hdr_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + stbi__start_write_callbacks(&s, func, context); + return stbi_write_hdr_core(&s, x, y, comp, (float *) data); +} + +# ifndef STBI_WRITE_NO_STDIO +int stbi_write_hdr(char const *filename, int x, int y, int comp, const float *data) +{ + stbi__write_context s; + if (stbi__start_write_file(&s,filename)) { + int r = stbi_write_hdr_core(&s, x, y, comp, (float *) data); + stbi__end_write_file(&s); + return r; + } else + return 0; +} +# endif // STBI_WRITE_NO_STDIO + + +////////////////////////////////////////////////////////////////////////////// +// +// PNG writer +// + +// stretchy buffer; stbiw__sbpush() == vector<>::push_back() -- stbiw__sbcount() == vector<>::size() +# define stbiw__sbraw(a) ((int *) (a) - 2) +# define stbiw__sbm(a) stbiw__sbraw(a)[0] +# define stbiw__sbn(a) stbiw__sbraw(a)[1] + +# define stbiw__sbneedgrow(a,n) ((a)==0 || stbiw__sbn(a)+n >= stbiw__sbm(a)) +# define stbiw__sbmaybegrow(a,n) (stbiw__sbneedgrow(a,(n)) ? stbiw__sbgrow(a,n) : 0) +# define stbiw__sbgrow(a,n) stbiw__sbgrowf((void **) &(a), (n), sizeof(*(a))) + +# define stbiw__sbpush(a, v) (stbiw__sbmaybegrow(a,1), (a)[stbiw__sbn(a)++] = (v)) +# define stbiw__sbcount(a) ((a) ? stbiw__sbn(a) : 0) +# define stbiw__sbfree(a) ((a) ? STBIW_FREE(stbiw__sbraw(a)),0 : 0) + +static void *stbiw__sbgrowf(void **arr, int increment, int itemsize) +{ + int m = *arr ? 2*stbiw__sbm(*arr)+increment : increment+1; + void *p = STBIW_REALLOC_SIZED(*arr ? stbiw__sbraw(*arr) : 0, *arr ? (stbiw__sbm(*arr)*itemsize + sizeof(int)*2) : 0, itemsize * m + sizeof(int)*2); + STBIW_ASSERT(p); + if (p) { + if (!*arr) ((int *) p)[1] = 0; + *arr = (void *) ((int *) p + 2); + stbiw__sbm(*arr) = m; + } + return *arr; +} + +static unsigned char *stbiw__zlib_flushf(unsigned char *data, unsigned int *bitbuffer, int *bitcount) +{ + while (*bitcount >= 8) { + stbiw__sbpush(data, STBIW_UCHAR(*bitbuffer)); + *bitbuffer >>= 8; + *bitcount -= 8; + } + return data; +} + +static int stbiw__zlib_bitrev(int code, int codebits) +{ + int res=0; + while (codebits--) { + res = (res << 1) | (code & 1); + code >>= 1; + } + return res; +} + +static unsigned int stbiw__zlib_countm(unsigned char *a, unsigned char *b, int limit) +{ + int i; + for (i=0; i < limit && i < 258; ++i) + if (a[i] != b[i]) break; + return i; +} + +static unsigned int stbiw__zhash(unsigned char *data) +{ + stbiw_uint32 hash = data[0] + (data[1] << 8) + (data[2] << 16); + hash ^= hash << 3; + hash += hash >> 5; + hash ^= hash << 4; + hash += hash >> 17; + hash ^= hash << 25; + hash += hash >> 6; + return hash; +} + +# define stbiw__zlib_flush() (out = stbiw__zlib_flushf(out, &bitbuf, &bitcount)) +# define stbiw__zlib_add(code,codebits) \ +(bitbuf |= (code) << bitcount, bitcount += (codebits), stbiw__zlib_flush()) +# define stbiw__zlib_huffa(b,c) stbiw__zlib_add(stbiw__zlib_bitrev(b,c),c) +// default huffman tables +# define stbiw__zlib_huff1(n) stbiw__zlib_huffa(0x30 + (n), 8) +# define stbiw__zlib_huff2(n) stbiw__zlib_huffa(0x190 + (n)-144, 9) +# define stbiw__zlib_huff3(n) stbiw__zlib_huffa(0 + (n)-256,7) +# define stbiw__zlib_huff4(n) stbiw__zlib_huffa(0xc0 + (n)-280,8) +# define stbiw__zlib_huff(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : (n) <= 255 ? stbiw__zlib_huff2(n) : (n) <= 279 ? stbiw__zlib_huff3(n) : stbiw__zlib_huff4(n)) +# define stbiw__zlib_huffb(n) ((n) <= 143 ? stbiw__zlib_huff1(n) : stbiw__zlib_huff2(n)) + +# define stbiw__ZHASH 16384 + +unsigned char * stbi_zlib_compress(unsigned char *data, int data_len, int *out_len, int quality) +{ + static unsigned short lengthc[] = { 3,4,5,6,7,8,9,10,11,13,15,17,19,23,27,31,35,43,51,59,67,83,99,115,131,163,195,227,258, 259 }; + static unsigned char lengtheb[]= { 0,0,0,0,0,0,0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0 }; + static unsigned short distc[] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193,257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577, 32768 }; + static unsigned char disteb[] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13 }; + unsigned int bitbuf=0; + int i,j, bitcount=0; + unsigned char *out = NULL; + unsigned char ***hash_table = (unsigned char***) STBIW_MALLOC(stbiw__ZHASH * sizeof(char**)); + if (quality < 5) quality = 5; + + stbiw__sbpush(out, 0x78); // DEFLATE 32K window + stbiw__sbpush(out, 0x5e); // FLEVEL = 1 + stbiw__zlib_add(1,1); // BFINAL = 1 + stbiw__zlib_add(1,2); // BTYPE = 1 -- fixed huffman + + for (i=0; i < stbiw__ZHASH; ++i) + hash_table[i] = NULL; + + i=0; + while (i < data_len-3) { + // hash next 3 bytes of data to be compressed + int h = stbiw__zhash(data+i)&(stbiw__ZHASH-1), best=3; + unsigned char *bestloc = 0; + unsigned char **hlist = hash_table[h]; + int n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32768) { // if entry lies within window + int d = stbiw__zlib_countm(hlist[j], data+i, data_len-i); + if (d >= best) best=d,bestloc=hlist[j]; + } + } + // when hash table entry is too long, delete half the entries + if (hash_table[h] && stbiw__sbn(hash_table[h]) == 2*quality) { + STBIW_MEMMOVE(hash_table[h], hash_table[h]+quality, sizeof(hash_table[h][0])*quality); + stbiw__sbn(hash_table[h]) = quality; + } + stbiw__sbpush(hash_table[h],data+i); + + if (bestloc) { + // "lazy matching" - check match at *next* byte, and if it's better, do cur byte as literal + h = stbiw__zhash(data+i+1)&(stbiw__ZHASH-1); + hlist = hash_table[h]; + n = stbiw__sbcount(hlist); + for (j=0; j < n; ++j) { + if (hlist[j]-data > i-32767) { + int e = stbiw__zlib_countm(hlist[j], data+i+1, data_len-i-1); + if (e > best) { // if next match is better, bail on current match + bestloc = NULL; + break; + } + } + } + } + + if (bestloc) { + int d = (int) (data+i - bestloc); // distance back + STBIW_ASSERT(d <= 32767 && best <= 258); + for (j=0; best > lengthc[j+1]-1; ++j); + stbiw__zlib_huff(j+257); + if (lengtheb[j]) stbiw__zlib_add(best - lengthc[j], lengtheb[j]); + for (j=0; d > distc[j+1]-1; ++j); + stbiw__zlib_add(stbiw__zlib_bitrev(j,5),5); + if (disteb[j]) stbiw__zlib_add(d - distc[j], disteb[j]); + i += best; + } else { + stbiw__zlib_huffb(data[i]); + ++i; + } + } + // write out final bytes + for (;i < data_len; ++i) + stbiw__zlib_huffb(data[i]); + stbiw__zlib_huff(256); // end of block + // pad with 0 bits to byte boundary + while (bitcount) + stbiw__zlib_add(0,1); + + for (i=0; i < stbiw__ZHASH; ++i) + (void) stbiw__sbfree(hash_table[i]); + STBIW_FREE(hash_table); + + { + // compute adler32 on input + unsigned int s1=1, s2=0; + int blocklen = (int) (data_len % 5552); + j=0; + while (j < data_len) { + for (i=0; i < blocklen; ++i) s1 += data[j+i], s2 += s1; + s1 %= 65521, s2 %= 65521; + j += blocklen; + blocklen = 5552; + } + stbiw__sbpush(out, STBIW_UCHAR(s2 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s2)); + stbiw__sbpush(out, STBIW_UCHAR(s1 >> 8)); + stbiw__sbpush(out, STBIW_UCHAR(s1)); + } + *out_len = stbiw__sbn(out); + // make returned pointer freeable + STBIW_MEMMOVE(stbiw__sbraw(out), out, *out_len); + return (unsigned char *) stbiw__sbraw(out); +} + +static unsigned int stbiw__crc32(unsigned char *buffer, int len) +{ + static unsigned int crc_table[256] = + { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, + 0x0eDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, + 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, + 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, + 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, + 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, + 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, + 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, + 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, + 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, + 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, + 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, + 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, + 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, + 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, + 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, + 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, + 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, + 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, + 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, + 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, + 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, + 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, + 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, + 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, + 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, + 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, + 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, + 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + unsigned int crc = ~0u; + int i; + for (i=0; i < len; ++i) + crc = (crc >> 8) ^ crc_table[buffer[i] ^ (crc & 0xff)]; + return ~crc; +} + +# define stbiw__wpng4(o,a,b,c,d) ((o)[0]=STBIW_UCHAR(a),(o)[1]=STBIW_UCHAR(b),(o)[2]=STBIW_UCHAR(c),(o)[3]=STBIW_UCHAR(d),(o)+=4) +# define stbiw__wp32(data,v) stbiw__wpng4(data, (v)>>24,(v)>>16,(v)>>8,(v)); +# define stbiw__wptag(data,s) stbiw__wpng4(data, s[0],s[1],s[2],s[3]) + +static void stbiw__wpcrc(unsigned char **data, int len) +{ + unsigned int crc = stbiw__crc32(*data - len - 4, len+4); + stbiw__wp32(*data, crc); +} + +static unsigned char stbiw__paeth(int a, int b, int c) +{ + int p = a + b - c, pa = abs(p-a), pb = abs(p-b), pc = abs(p-c); + if (pa <= pb && pa <= pc) return STBIW_UCHAR(a); + if (pb <= pc) return STBIW_UCHAR(b); + return STBIW_UCHAR(c); +} + +// @OPTIMIZE: provide an option that always forces left-predict or paeth predict +unsigned char *stbi_write_png_to_mem(unsigned char *pixels, int stride_bytes, int x, int y, int n, int *out_len) +{ + int ctype[5] = { -1, 0, 4, 2, 6 }; + unsigned char sig[8] = { 137,80,78,71,13,10,26,10 }; + unsigned char *out,*o, *filt, *zlib; + signed char *line_buffer; + int i,j,k,p,zlen; + + if (stride_bytes == 0) + stride_bytes = x * n; + + filt = (unsigned char *) STBIW_MALLOC((x*n+1) * y); if (!filt) return 0; + line_buffer = (signed char *) STBIW_MALLOC(x * n); if (!line_buffer) { STBIW_FREE(filt); return 0; } + for (j=0; j < y; ++j) { + static int mapping[] = { 0,1,2,3,4 }; + static int firstmap[] = { 0,1,0,5,6 }; + int *mymap = (j != 0) ? mapping : firstmap; + int best = 0, bestval = 0x7fffffff; + for (p=0; p < 2; ++p) { + for (k= p?best:0; k < 5; ++k) { // @TODO: clarity: rewrite this to go 0..5, and 'continue' the unwanted ones during 2nd pass + int type = mymap[k],est=0; + unsigned char *z = pixels + stride_bytes*j; + for (i=0; i < n; ++i) + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - (z[i-stride_bytes]>>1); break; + case 4: line_buffer[i] = (signed char) (z[i] - stbiw__paeth(0,z[i-stride_bytes],0)); break; + case 5: line_buffer[i] = z[i]; break; + case 6: line_buffer[i] = z[i]; break; + } + for (i=n; i < x*n; ++i) { + switch (type) { + case 0: line_buffer[i] = z[i]; break; + case 1: line_buffer[i] = z[i] - z[i-n]; break; + case 2: line_buffer[i] = z[i] - z[i-stride_bytes]; break; + case 3: line_buffer[i] = z[i] - ((z[i-n] + z[i-stride_bytes])>>1); break; + case 4: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], z[i-stride_bytes], z[i-stride_bytes-n]); break; + case 5: line_buffer[i] = z[i] - (z[i-n]>>1); break; + case 6: line_buffer[i] = z[i] - stbiw__paeth(z[i-n], 0,0); break; + } + } + if (p) break; + for (i=0; i < x*n; ++i) + est += abs((signed char) line_buffer[i]); + if (est < bestval) { bestval = est; best = k; } + } + } + // when we get here, best contains the filter type, and line_buffer contains the data + filt[j*(x*n+1)] = (unsigned char) best; + STBIW_MEMMOVE(filt+j*(x*n+1)+1, line_buffer, x*n); + } + STBIW_FREE(line_buffer); + zlib = stbi_zlib_compress(filt, y*( x*n+1), &zlen, 8); // increase 8 to get smaller but use more memory + STBIW_FREE(filt); + if (!zlib) return 0; + + // each tag requires 12 bytes of overhead + out = (unsigned char *) STBIW_MALLOC(8 + 12+13 + 12+zlen + 12); + if (!out) return 0; + *out_len = 8 + 12+13 + 12+zlen + 12; + + o=out; + STBIW_MEMMOVE(o,sig,8); o+= 8; + stbiw__wp32(o, 13); // header length + stbiw__wptag(o, "IHDR"); + stbiw__wp32(o, x); + stbiw__wp32(o, y); + *o++ = 8; + *o++ = STBIW_UCHAR(ctype[n]); + *o++ = 0; + *o++ = 0; + *o++ = 0; + stbiw__wpcrc(&o,13); + + stbiw__wp32(o, zlen); + stbiw__wptag(o, "IDAT"); + STBIW_MEMMOVE(o, zlib, zlen); + o += zlen; + STBIW_FREE(zlib); + stbiw__wpcrc(&o, zlen); + + stbiw__wp32(o,0); + stbiw__wptag(o, "IEND"); + stbiw__wpcrc(&o,0); + + STBIW_ASSERT(o == out + *out_len); + + return out; +} + +# ifndef STBI_WRITE_NO_STDIO +STBIWDEF int stbi_write_png(char const *filename, int x, int y, int comp, const void *data, int stride_bytes) +{ + FILE *f; + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + f = fopen(filename, "wb"); + if (!f) { STBIW_FREE(png); return 0; } + fwrite(png, 1, len, f); + fclose(f); + STBIW_FREE(png); + return 1; +} +# endif + +STBIWDEF int stbi_write_png_to_func(stbi_write_func *func, void *context, int x, int y, int comp, const void *data, int stride_bytes) +{ + int len; + unsigned char *png = stbi_write_png_to_mem((unsigned char *) data, stride_bytes, x, y, comp, &len); + if (png == NULL) return 0; + func(context, png, len); + STBIW_FREE(png); + return 1; +} + +#endif // STB_IMAGE_WRITE_IMPLEMENTATION + +/* Revision history +1.04 (2017-03-03) +monochrome BMP expansion +1.03 ??? +1.02 (2016-04-02) +avoid allocating large structures on the stack +1.01 (2016-01-16) +STBIW_REALLOC_SIZED: support allocators with no realloc support +avoid race-condition in crc initialization +minor compile issues +1.00 (2015-09-14) +installable file IO function +0.99 (2015-09-13) +warning fixes; TGA rle support +0.98 (2015-04-08) +added STBIW_MALLOC, STBIW_ASSERT etc +0.97 (2015-01-18) +fixed HDR asserts, rewrote HDR rle logic +0.96 (2015-01-17) +add HDR output +fix monochrome BMP +0.95 (2014-08-17) +add monochrome TGA output +0.94 (2014-05-31) +rename private functions to avoid conflicts with stb_image.h +0.93 (2014-05-27) +warning fixes +0.92 (2010-08-01) +casts to unsigned char to fix warnings +0.91 (2010-07-17) +first public release +0.90 first internal release + */ + + /* +------------------------------------------------------------------------------ +This software is available under 2 licenses -- choose whichever you prefer. +------------------------------------------------------------------------------ +ALTERNATIVE A - MIT License +Copyright (c) 2017 Sean Barrett +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. +------------------------------------------------------------------------------ +ALTERNATIVE B - Public Domain (www.unlicense.org) +This is free and unencumbered software released into the public domain. +Anyone is free to copy, modify, publish, use, compile, sell, or distribute this +software, either in source code form or as a compiled binary, for any purpose, +commercial or non-commercial, and by any means. +In jurisdictions that recognize copyright laws, the author or authors of this +software dedicate any and all copyright interest in the software to the public +domain. We make this dedication for the benefit of the public at large and to +the detriment of our heirs and successors. We intend this dedication to be an +overt act of relinquishment in perpetuity of all present and future rights to +this software under copyright law. +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 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. +------------------------------------------------------------------------------ +*/ diff --git a/include/camera.h b/include/camera.h new file mode 100644 index 0000000..5207d3d --- /dev/null +++ b/include/camera.h @@ -0,0 +1,28 @@ +#pragma once + +#include "ray.h" + +class Camera { +public: + Camera() : position(glm::vec3(0)), direction(glm::vec3(0)) {} + Camera(glm::vec3 position, glm::vec3 direction) : position(position), direction(direction) {} + + void look_at(glm::vec3 eye, glm::vec3 target) { + position = eye; + direction = glm::normalize(target - eye); + } + + Ray get_ray(const int32_t x, const int32_t y, const int32_t width, const int32_t height) const { + const glm::vec3 up = glm::vec3(0, 1, 0); + const glm::vec3 right = glm::normalize(glm::cross(direction, up)); + + const float h2 = height / 2.0f; + const float w2 = width / 2.0f; + + glm::vec3 ray_dir = position + (h2 / tan(glm::radians(fov) / 2)) * direction + (y - h2) * up + (float)(x - w2) * right; + return Ray(position, ray_dir); + } + + glm::vec3 position, direction; + float fov = 45.0f; +}; diff --git a/include/image.h b/include/image.h new file mode 100644 index 0000000..51182fd --- /dev/null +++ b/include/image.h @@ -0,0 +1,17 @@ +#pragma once + +#include + +template +class Image { +public: + T& get(const int32_t x, const int32_t y) { + return array[y * Width + x]; + } + + T get(const int32_t x, const int32_t y) const { + return array[y * Width + x]; + } + + T array[Width * Height] = {}; +}; diff --git a/include/intersections.h b/include/intersections.h new file mode 100644 index 0000000..5b158d8 --- /dev/null +++ b/include/intersections.h @@ -0,0 +1,64 @@ +#pragma once + +#include + +#include "ray.h" + +constexpr float epsilon = std::numeric_limits().epsilon(); + +namespace intersections { + bool ray_sphere(const Ray ray, const glm::vec4 sphere) { + const glm::vec3 diff = ray.origin - glm::vec3(sphere); + const float b = glm::dot(ray.direction, diff); + const float c = glm::dot(diff, diff) - sphere.w * sphere.w; + float t = b * b - c; + + if (t > 0.0) { + t = -b - glm::sqrt(t); + if (t > 0.0) + return true; + } + + return false; + } + + float ray_triangle(const Ray ray, + const glm::vec3 v0, + const glm::vec3 v1, + const glm::vec3 v2, + float& t, + float& u, + float& v) { + glm::vec3 e1 = v1 - v0; + glm::vec3 e2 = v2 - v0; + + glm::vec3 pvec = glm::cross(ray.direction, e2); + float det = glm::dot(e1, pvec); + + // if determinant is zero then ray is + // parallel with the triangle plane + if (det > -epsilon && det < epsilon) return false; + float invdet = 1.0/det; + + // calculate distance from m[0] to origin + glm::vec3 tvec = ray.origin - v0; + + // u and v are the barycentric coordinates + // in triangle if u >= 0, v >= 0 and u + v <= 1 + u = glm::dot(tvec, pvec) * invdet; + + // check against one edge and opposite point + if (u < 0.0 || u > 1.0) return false; + + glm::vec3 qvec = glm::cross(tvec, e1); + v = glm::dot(ray.direction, qvec) * invdet; + + // check against other edges + if (v < 0.0 || u + v > 1.0) return false; + + //distance along the ray, i.e. intersect at o + t * d + t = glm::dot(e2, qvec) * invdet; + + return true; + } +}; diff --git a/include/lighting.h b/include/lighting.h new file mode 100644 index 0000000..426a425 --- /dev/null +++ b/include/lighting.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +namespace lighting { + float point_light(const glm::vec3 pos, const glm::vec3 light, const glm::vec3 normal) { + const glm::vec3 dir = light - pos; + const float n_dot_l = glm::max(glm::dot(normal, glm::normalize(dir)), 0.0f); + + return n_dot_l; + } +}; diff --git a/include/ray.h b/include/ray.h new file mode 100644 index 0000000..730b99f --- /dev/null +++ b/include/ray.h @@ -0,0 +1,9 @@ +#pragma once + +#include + +struct Ray { + Ray(const glm::vec3 origin, const glm::vec3 direction) : origin(origin), direction(direction) {} + + glm::vec3 origin, direction; +}; diff --git a/include/scene.h b/include/scene.h new file mode 100644 index 0000000..5211d94 --- /dev/null +++ b/include/scene.h @@ -0,0 +1,93 @@ +#pragma once + +#include + +#include + +struct Scene { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + + bool load_from_file(const std::string_view path) { + return tinyobj::LoadObj(&attrib, &shapes, &materials, nullptr, path.data()); + } +}; + +inline glm::vec3 fetch_position(const Scene& scene, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex) { + tinyobj::index_t idx = mesh.indices[(index * 3) +vertex]; + + tinyobj::real_t vx = scene.attrib.vertices[3*idx.vertex_index+0]; + tinyobj::real_t vy = scene.attrib.vertices[3*idx.vertex_index+1]; + tinyobj::real_t vz = scene.attrib.vertices[3*idx.vertex_index+2]; + + return glm::vec3(vx, vy, vz); +} + +inline glm::vec3 fetch_normal(const Scene& scene, const tinyobj::mesh_t& mesh, const int32_t index, const int32_t vertex) { + tinyobj::index_t idx = mesh.indices[(index * 3) + vertex]; + + tinyobj::real_t nx = scene.attrib.normals[3*idx.normal_index+0]; + tinyobj::real_t ny = scene.attrib.normals[3*idx.normal_index+1]; + tinyobj::real_t nz = scene.attrib.normals[3*idx.normal_index+2]; + + return glm::vec3(nx, ny, nz); +} + +struct HitResult { + glm::vec3 position, normal; + tinyobj::mesh_t* mesh = nullptr; +}; + + +std::optional test_mesh(const Ray ray, const Scene& scene, const tinyobj::mesh_t& mesh, float& tClosest) { + bool intersection = false; + HitResult result = {}; + + for (size_t i = 0; i < mesh.num_face_vertices.size(); i++) { + const glm::vec3 v0 = fetch_position(scene, mesh, i, 0); + const glm::vec3 v1 = fetch_position(scene, mesh, i, 1); + const glm::vec3 v2 = fetch_position(scene, mesh, i, 2); + + float t = std::numeric_limits::infinity(), u, v; + if (intersections::ray_triangle(ray, v0, v1, v2, t, u, v)) { + if (t < tClosest && t > epsilon) { + const glm::vec3 n0 = fetch_normal(scene, mesh, i, 0); + const glm::vec3 n1 = fetch_normal(scene, mesh, i, 1); + const glm::vec3 n2 = fetch_normal(scene, mesh, i, 2); + + result.normal = (1 - u - v) * n0 + u * n1 + v * n2; + result.position = ray.origin + ray.direction * t; + + tClosest = t; + + intersection = true; + } + } + } + + if(intersection) + return result; + else + return {}; +} + +std::optional test_scene(const Ray ray, const Scene& scene, float tClosest = std::numeric_limits::infinity()) { + bool intersection = false; + HitResult result = {}; + + for (uint32_t i = 0; i < scene.shapes.size(); i++) { + auto mesh = scene.shapes[i].mesh; + + if(auto hit = test_mesh(ray, scene, mesh, tClosest)) { + intersection = true; + result = hit.value(); + result.mesh = &mesh; + } + } + + if(intersection) + return result; + else + return {}; +} diff --git a/include/tiny_obj_loader.h b/include/tiny_obj_loader.h new file mode 100644 index 0000000..ee44076 --- /dev/null +++ b/include/tiny_obj_loader.h @@ -0,0 +1,2029 @@ +/* +The MIT License (MIT) + +Copyright (c) 2012-2016 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 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 { + +// 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 + +#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; + +typedef struct { + 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 (default = ?) TODO + 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) +} texture_option_t; + +typedef struct { + 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, bump + std::string displacement_texname; // disp + std::string alpha_texname; // map_d + + 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; + + // 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; + real_t pad1; + 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; +} material_t; + +typedef struct { + std::string name; + + std::vector intValues; + std::vector floatValues; + std::vector stringValues; +} tag_t; + +// Index struct to support different indices for vtx/normal/texcoord. +// -1 means not used. +typedef struct { + int vertex_index; + int normal_index; + int texcoord_index; +} index_t; + +typedef struct { + std::vector indices; + std::vector num_face_vertices; // The number of vertices per + // face. 3 = polygon, 4 = quad, + // ... Up to 255. + std::vector material_ids; // per-face material ID + std::vector tags; // SubD tag +} mesh_t; + +typedef struct { + std::string name; + mesh_t mesh; +} shape_t; + +// Vertex attributes +typedef struct { + std::vector vertices; // 'v' + std::vector normals; // 'vn' + std::vector texcoords; // 'vt' +} attrib_t; + +typedef 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) {} +} callback_t; + +class MaterialReader { + public: + MaterialReader() {} + virtual ~MaterialReader(); + + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *err) = 0; +}; + +class MaterialFileReader : public MaterialReader { + public: + explicit MaterialFileReader(const std::string &mtl_basedir) + : m_mtlBaseDir(mtl_basedir) {} + virtual ~MaterialFileReader() {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *err); + + private: + std::string m_mtlBaseDir; +}; + +class MaterialStreamReader : public MaterialReader { + public: + explicit MaterialStreamReader(std::istream &inStream) + : m_inStream(inStream) {} + virtual ~MaterialStreamReader() {} + virtual bool operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, std::string *err); + + private: + std::istream &m_inStream; +}; + +/// Loads .obj from a file. +/// 'attrib', 'shapes' and 'materials' will be filled with parsed shape data +/// 'shapes' will be filled with parsed shape data +/// Returns true when loading .obj become success. +/// Returns warning 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. +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *err, + const char *filename, const char *mtl_basedir = NULL, + bool triangulate = 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 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 *err = NULL); + +/// Loads object from a std::istream, uses GetMtlIStreamFn 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 *err, + std::istream *inStream, MaterialReader *readMatFn = NULL, + bool triangulate = true); + +/// Loads materials into std::map +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning); + +} // namespace tinyobj + +#endif // TINY_OBJ_LOADER_H_ + +#ifdef TINYOBJLOADER_IMPLEMENTATION +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +namespace tinyobj { + +MaterialReader::~MaterialReader() {} + +#define TINYOBJ_SSCANF_BUFFER_SIZE (4096) + +struct vertex_index { + int v_idx, vt_idx, vn_idx; + vertex_index() : v_idx(-1), vt_idx(-1), vn_idx(-1) {} + explicit vertex_index(int idx) : v_idx(idx), vt_idx(idx), vn_idx(idx) {} + vertex_index(int vidx, int vtidx, int vnidx) + : v_idx(vidx), vt_idx(vtidx), vn_idx(vnidx) {} +}; + +struct 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; +}; + +// See +// http://stackoverflow.com/questions/6089231/getting-std-ifstream-to-handle-lf-cr-and-crlf +static std::istream &safeGetline(std::istream &is, std::string &t) { + t.clear(); + + // The characters in the stream are read one-by-one using a std::streambuf. + // That is faster than reading them one-by-one using the std::istream. + // Code that uses streambuf this way must be guarded by a sentry object. + // The sentry object performs various tasks, + // such as thread synchronization and updating the stream state. + + std::istream::sentry se(is, true); + std::streambuf *sb = is.rdbuf(); + + for (;;) { + int c = sb->sbumpc(); + switch (c) { + case '\n': + return is; + case '\r': + if (sb->sgetc() == '\n') sb->sbumpc(); + return is; + case EOF: + // Also handle the case when the last line has no line ending + if (t.empty()) is.setstate(std::ios::eofbit); + return is; + default: + t += static_cast(c); + } + } +} + +#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 int fixIndex(int idx, int n) { + if (idx > 0) return idx - 1; + if (idx == 0) return 0; + return n + idx; // negative value = relative +} + +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; + + /* + BEGIN PARSING. + */ + + // Find out what sign we've got. + if (*curr == '+' || *curr == '-') { + sign = *curr; + curr++; + } else if (IS_DIGIT(*curr)) { /* Pass through. */ + } else { + goto fail; + } + + // Read the integer part. + end_not_reached = (curr != s_end); + 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)) { + 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 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); +} + +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; + + ts.num_ints = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + (*token)++; + + ts.num_reals = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return ts; + } + (*token)++; + + ts.num_strings = atoi((*token)); + (*token) += strcspn((*token), "/ \t\r") + 1; + + return ts; +} + +// Parse triples with index offsets: i, i/j/k, i//k, i/j +static vertex_index parseTriple(const char **token, int vsize, int vnsize, + int vtsize) { + vertex_index vi(-1); + + vi.v_idx = fixIndex(atoi((*token)), vsize); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + (*token)++; + + // i//k + if ((*token)[0] == '/') { + (*token)++; + vi.vn_idx = fixIndex(atoi((*token)), vnsize); + (*token) += strcspn((*token), "/ \t\r"); + return vi; + } + + // i/j/k or i/j + vi.vt_idx = fixIndex(atoi((*token)), vtsize); + (*token) += strcspn((*token), "/ \t\r"); + if ((*token)[0] != '/') { + return vi; + } + + // i/j/k + (*token)++; // skip '/' + vi.vn_idx = fixIndex(atoi((*token)), vnsize); + (*token) += strcspn((*token), "/ \t\r"); + return vi; +} + +// Parse raw triples: i, i/j/k, i//k, i/j +static vertex_index parseRawTriple(const char **token) { + vertex_index 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; +} + +static bool ParseTextureNameAndOption(std::string *texname, + texture_option_t *texopt, + const char *linebuf, const bool is_bump) { + // @todo { write more robust lexer and parser. } + bool found_texname = false; + std::string texture_name; + + // Fill with default value for texopt. + if (is_bump) { + texopt->imfchan = 'l'; + } else { + texopt->imfchan = 'm'; + } + texopt->bump_multiplier = 1.0f; + texopt->clamp = false; + texopt->blendu = true; + texopt->blendv = true; + texopt->sharpness = 1.0f; + texopt->brightness = 0.0f; + texopt->contrast = 1.0f; + texopt->origin_offset[0] = 0.0f; + texopt->origin_offset[1] = 0.0f; + texopt->origin_offset[2] = 0.0f; + texopt->scale[0] = 1.0f; + texopt->scale[1] = 1.0f; + texopt->scale[2] = 1.0f; + texopt->turbulence[0] = 0.0f; + texopt->turbulence[1] = 0.0f; + texopt->turbulence[2] = 0.0f; + texopt->type = TEXTURE_TYPE_NONE; + + const char *token = linebuf; // Assume line ends with NULL + + while (!IS_NEW_LINE((*token))) { + 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, "-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 { + // Assume texture filename + token += strspn(token, " \t"); // skip space + 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 + + found_texname = true; + } + } + + if (found_texname) { + (*texname) = texture_name; + return true; + } else { + return false; + } +} + +static void InitMaterial(material_t *material) { + material->name = ""; + material->ambient_texname = ""; + material->diffuse_texname = ""; + material->specular_texname = ""; + material->specular_highlight_texname = ""; + material->bump_texname = ""; + material->displacement_texname = ""; + material->alpha_texname = ""; + for (int i = 0; i < 3; i++) { + material->ambient[i] = 0.f; + material->diffuse[i] = 0.f; + material->specular[i] = 0.f; + material->transmittance[i] = 0.f; + material->emission[i] = 0.f; + } + material->illum = 0; + material->dissolve = 1.f; + material->shininess = 1.f; + material->ior = 1.f; + + material->roughness = 0.f; + material->metallic = 0.f; + material->sheen = 0.f; + material->clearcoat_thickness = 0.f; + material->clearcoat_roughness = 0.f; + material->anisotropy_rotation = 0.f; + material->anisotropy = 0.f; + material->roughness_texname = ""; + material->metallic_texname = ""; + material->sheen_texname = ""; + material->emissive_texname = ""; + material->normal_texname = ""; + + material->unknown_parameter.clear(); +} + +static bool exportFaceGroupToShape( + shape_t *shape, const std::vector > &faceGroup, + const std::vector &tags, const int material_id, + const std::string &name, bool triangulate) { + if (faceGroup.empty()) { + return false; + } + + // Flatten vertices and indices + for (size_t i = 0; i < faceGroup.size(); i++) { + const std::vector &face = faceGroup[i]; + + vertex_index i0 = face[0]; + vertex_index i1(-1); + vertex_index i2 = face[1]; + + size_t npolys = face.size(); + + if (triangulate) { + // Polygon -> triangle fan conversion + for (size_t k = 2; k < npolys; k++) { + i1 = i2; + i2 = face[k]; + + 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); + } + } else { + for (size_t k = 0; k < npolys; k++) { + index_t idx; + idx.vertex_index = face[k].v_idx; + idx.normal_index = face[k].vn_idx; + idx.texcoord_index = face[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->name = name; + shape->mesh.tags = tags; + + return true; +} + +// Split a string with specified delimiter character. +// http://stackoverflow.com/questions/236129/split-a-string-in-c +static void SplitString(const std::string &s, char delim, + std::vector &elems) { + std::stringstream ss; + ss.str(s); + std::string item; + while (std::getline(ss, item, delim)) { + elems.push_back(item); + } +} + +void LoadMtl(std::map *material_map, + std::vector *materials, std::istream *inStream, + std::string *warning) { + // 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; + + std::stringstream ss; + + std::string linebuf; + while (inStream->peek() != -1) { + safeGetline(*inStream, linebuf); + + // Trim trailing whitespace. + if (linebuf.size() > 0) { + linebuf = linebuf.substr(0, linebuf.find_last_not_of(" \t") + 1); + } + + // Trim newline '\r\n' or '\n' + if (linebuf.size() > 0) { + 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 + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + std::sscanf(token, "%s", namebuf); +#endif + material.name = namebuf; + 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; + 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) { + ss << "WARN: Both `d` and `Tr` parameters defined for \"" + << material.name << "\". Use the value of `d` for dissolve." + << std::endl; + } + 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. + ss << "WARN: Both `d` and `Tr` parameters defined for \"" + << material.name << "\". Use the value of `d` for dissolve." + << std::endl; + } 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 = 1.0f - 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, + /* is_bump */ false); + continue; + } + + // diffuse texture + if ((0 == strncmp(token, "map_Kd", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.diffuse_texname), + &(material.diffuse_texopt), token, + /* is_bump */ false); + continue; + } + + // specular texture + if ((0 == strncmp(token, "map_Ks", 6)) && IS_SPACE(token[6])) { + token += 7; + ParseTextureNameAndOption(&(material.specular_texname), + &(material.specular_texopt), token, + /* is_bump */ false); + 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, + /* is_bump */ false); + continue; + } + + // bump texture + if ((0 == strncmp(token, "map_bump", 8)) && IS_SPACE(token[8])) { + token += 9; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token, + /* is_bump */ true); + continue; + } + + // bump texture + if ((0 == strncmp(token, "bump", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.bump_texname), + &(material.bump_texopt), token, + /* is_bump */ true); + 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, + /* is_bump */ false); + continue; + } + + // displacement texture + if ((0 == strncmp(token, "disp", 4)) && IS_SPACE(token[4])) { + token += 5; + ParseTextureNameAndOption(&(material.displacement_texname), + &(material.displacement_texopt), token, + /* is_bump */ false); + 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, + /* is_bump */ false); + 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, + /* is_bump */ false); + 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, + /* is_bump */ false); + 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, + /* is_bump */ false); + 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, + /* is_bump */ false); // @fixme { is_bump will be true? } + 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) = ss.str(); + } +} + +bool MaterialFileReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *err) { + std::string filepath; + + if (!m_mtlBaseDir.empty()) { + filepath = std::string(m_mtlBaseDir) + matId; + } else { + filepath = matId; + } + + std::ifstream matIStream(filepath.c_str()); + if (!matIStream) { + std::stringstream ss; + ss << "WARN: Material file [ " << filepath << " ] not found." << std::endl; + if (err) { + (*err) += ss.str(); + } + return false; + } + + std::string warning; + LoadMtl(matMap, materials, &matIStream, &warning); + + if (!warning.empty()) { + if (err) { + (*err) += warning; + } + } + + return true; +} + +bool MaterialStreamReader::operator()(const std::string &matId, + std::vector *materials, + std::map *matMap, + std::string *err) { + (void)matId; + if (!m_inStream) { + std::stringstream ss; + ss << "WARN: Material stream in error state. " << std::endl; + if (err) { + (*err) += ss.str(); + } + return false; + } + + std::string warning; + LoadMtl(matMap, materials, &m_inStream, &warning); + + if (!warning.empty()) { + if (err) { + (*err) += warning; + } + } + + return true; +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *err, + const char *filename, const char *mtl_basedir, bool trianglulate) { + attrib->vertices.clear(); + attrib->normals.clear(); + attrib->texcoords.clear(); + shapes->clear(); + + std::stringstream errss; + + std::ifstream ifs(filename); + if (!ifs) { + errss << "Cannot open file [" << filename << "]" << std::endl; + if (err) { + (*err) = errss.str(); + } + return false; + } + + std::string baseDir; + if (mtl_basedir) { + baseDir = mtl_basedir; + } + MaterialFileReader matFileReader(baseDir); + + return LoadObj(attrib, shapes, materials, err, &ifs, &matFileReader, + trianglulate); +} + +bool LoadObj(attrib_t *attrib, std::vector *shapes, + std::vector *materials, std::string *err, + std::istream *inStream, MaterialReader *readMatFn /*= NULL*/, + bool triangulate) { + std::stringstream errss; + + std::vector v; + std::vector vn; + std::vector vt; + std::vector tags; + std::vector > faceGroup; + std::string name; + + // material + std::map material_map; + int material = -1; + + shape_t shape; + + 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; + real_t x, y, z; + parseReal3(&x, &y, &z, &token); + v.push_back(x); + v.push_back(y); + v.push_back(z); + 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; + } + + // face + if (token[0] == 'f' && IS_SPACE((token[1]))) { + token += 2; + token += strspn(token, " \t"); + + std::vector face; + face.reserve(3); + + while (!IS_NEW_LINE(token[0])) { + vertex_index vi = parseTriple(&token, static_cast(v.size() / 3), + static_cast(vn.size() / 3), + static_cast(vt.size() / 2)); + face.push_back(vi); + size_t n = strspn(token, " \t\r"); + token += n; + } + + // replace with emplace_back + std::move on C++11 + faceGroup.push_back(std::vector()); + faceGroup[faceGroup.size() - 1].swap(face); + + continue; + } + + // use mtl + if ((0 == strncmp(token, "usemtl", 6)) && IS_SPACE((token[6]))) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + std::sscanf(token, "%s", namebuf); +#endif + + int newMaterialId = -1; + if (material_map.find(namebuf) != material_map.end()) { + newMaterialId = material_map[namebuf]; + } else { + // { error!! material not found } + } + + if (newMaterialId != material) { + // Create per-face material. Thus we don't add `shape` to `shapes` at + // this time. + // just clear `faceGroup` after `exportFaceGroupToShape()` call. + exportFaceGroupToShape(&shape, faceGroup, tags, material, name, + triangulate); + faceGroup.clear(); + 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 (err) { + (*err) += + "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 err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), materials, + &material_map, &err_mtl); + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; // This should be warn message. + } + + if (ok) { + found = true; + break; + } + } + + if (!found) { + if (err) { + (*err) += + "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 = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, + triangulate); + if (ret) { + shapes->push_back(shape); + } + + shape = shape_t(); + + // material = -1; + faceGroup.clear(); + + std::vector names; + names.reserve(2); + + 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); + + // names[0] must be 'g', so skip the 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name = ""; + } + + continue; + } + + // object name + if (token[0] == 'o' && IS_SPACE((token[1]))) { + // flush previous face group. + bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, + triangulate); + if (ret) { + shapes->push_back(shape); + } + + // material = -1; + faceGroup.clear(); + shape = shape_t(); + + // @todo { multiple object name? } + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + std::sscanf(token, "%s", namebuf); +#endif + name = std::string(namebuf); + + continue; + } + + if (token[0] == 't' && IS_SPACE(token[1])) { + tag_t tag; + + char namebuf[4096]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + std::sscanf(token, "%s", namebuf); +#endif + tag.name = std::string(namebuf); + + 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) { + char stringValueBuffer[4096]; + +#ifdef _MSC_VER + sscanf_s(token, "%s", stringValueBuffer, + (unsigned)_countof(stringValueBuffer)); +#else + std::sscanf(token, "%s", stringValueBuffer); +#endif + tag.stringValues[i] = stringValueBuffer; + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); + } + + // Ignore unknown command. + } + + bool ret = exportFaceGroupToShape(&shape, faceGroup, tags, material, name, + triangulate); + // exportFaceGroupToShape return false when `usemtl` is called in the last + // line. + // we also add `shape` to `shapes` when `shape.mesh` has already some + // faces(indices) + if (ret || shape.mesh.indices.size()) { + shapes->push_back(shape); + } + faceGroup.clear(); // for safety + + if (err) { + (*err) += errss.str(); + } + + attrib->vertices.swap(v); + attrib->normals.swap(vn); + attrib->texcoords.swap(vt); + + return true; +} + +bool LoadObjWithCallback(std::istream &inStream, const callback_t &callback, + void *user_data /*= NULL*/, + MaterialReader *readMatFn /*= NULL*/, + std::string *err /*= NULL*/) { + std::stringstream errss; + + // material + std::map material_map; + int material_id = -1; // -1 = invalid + + std::vector indices; + std::vector materials; + std::vector names; + names.reserve(2); + std::string name; + 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; + 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 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]))) { + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 7; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, + static_cast(_countof(namebuf))); +#else + std::sscanf(token, "%s", namebuf); +#endif + + int newMaterialId = -1; + if (material_map.find(namebuf) != material_map.end()) { + newMaterialId = material_map[namebuf]; + } else { + // { error!! material not found } + } + + if (newMaterialId != material_id) { + material_id = newMaterialId; + } + + if (callback.usemtl_cb) { + callback.usemtl_cb(user_data, namebuf, 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 (err) { + (*err) += + "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 err_mtl; + bool ok = (*readMatFn)(filenames[s].c_str(), &materials, + &material_map, &err_mtl); + if (err && (!err_mtl.empty())) { + (*err) += err_mtl; // This should be warn message. + } + + if (ok) { + found = true; + break; + } + } + + if (!found) { + if (err) { + (*err) += + "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); + + // names[0] must be 'g', so skip the 0th element. + if (names.size() > 1) { + name = names[1]; + } else { + name.clear(); + } + + 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? } + char namebuf[TINYOBJ_SSCANF_BUFFER_SIZE]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + std::sscanf(token, "%s", namebuf); +#endif + std::string object_name = std::string(namebuf); + + 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; + + char namebuf[4096]; + token += 2; +#ifdef _MSC_VER + sscanf_s(token, "%s", namebuf, (unsigned)_countof(namebuf)); +#else + std::sscanf(token, "%s", namebuf); +#endif + tag.name = std::string(namebuf); + + 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) { + char stringValueBuffer[4096]; + +#ifdef _MSC_VER + sscanf_s(token, "%s", stringValueBuffer, + (unsigned)_countof(stringValueBuffer)); +#else + std::sscanf(token, "%s", stringValueBuffer); +#endif + tag.stringValues[i] = stringValueBuffer; + token += tag.stringValues[i].size() + 1; + } + + tags.push_back(tag); + } +#endif + + // Ignore unknown command. + } + + if (err) { + (*err) += errss.str(); + } + + return true; +} +} // namespace tinyobj + +#endif diff --git a/misc/output.png b/misc/output.png new file mode 100644 index 0000000000000000000000000000000000000000..aaf44626e88cd274d651ba0d6a80f7fe10957091 GIT binary patch literal 23617 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 AhX4Qo literal 0 HcmV?d00001 diff --git a/src/main.cpp b/src/main.cpp new file mode 100644 index 0000000..c7d9808 --- /dev/null +++ b/src/main.cpp @@ -0,0 +1,99 @@ +#include +#include +#include +#include +#include + +#define STB_IMAGE_WRITE_IMPLEMENTATION +#include + +#include "intersections.h" +#include "camera.h" +#include "image.h" +#include "lighting.h" +#include "scene.h" + +#define TINYOBJLOADER_IMPLEMENTATION +#include + +// scene information +constexpr int32_t width = 512, height = 512; +constexpr glm::vec3 light_position = glm::vec3(5); +const Camera camera = [] { + Camera camera; + camera.look_at(glm::vec3(0, 0, 4), glm::vec3(0)); + + return camera; +}(); +constexpr std::string_view model_path = "suzanne.obj"; + +// internal variables +constexpr float light_bias = 0.01f; +constexpr int32_t tile_size = 32; +constexpr int32_t num_tiles_x = width / tile_size; +constexpr int32_t num_tiles_y = height / tile_size; + +// globals +Scene scene = {}; +Image colors = {}; + +bool calculate_tile(const int32_t from_x, const int32_t to_width, const int32_t from_y, const int32_t to_height) { + for (int32_t y = from_y; y < (from_y + to_height); y++) { + for (int32_t x = from_x; x < (from_x + to_width); x++) { + Ray ray_camera = camera.get_ray(x, y, width, height); + + if (auto hit = test_scene(ray_camera, scene)) { + const float diffuse = lighting::point_light(hit->position, light_position, hit->normal); + + //shadow calculation + bool blocked = false; + if(glm::dot(light_position - hit->position, hit->normal) > 0) { + const glm::vec3 light_dir = glm::normalize(light_position - hit->position); + + const Ray shadow_ray(hit->position + (hit->normal * light_bias), light_dir); + + if(test_scene(shadow_ray, scene)) + blocked = true; + } else { + blocked = true; + } + + const int32_t finalColor = diffuse * 255 * !blocked; + colors.get(x, y) = glm::ivec4(finalColor, finalColor, finalColor, 1); + } + } + } + + return true; +} + +int main(int, char*[]) { + if(!scene.load_from_file(model_path)) + return -1; + + std::vector> futures; + for(int32_t y = 0; y < num_tiles_y; y++) + for(int32_t x = 0; x < num_tiles_x; x++) + futures.push_back(std::async(std::launch::async, calculate_tile, x * tile_size, tile_size, y * tile_size, tile_size)); + + for(auto& future : futures) + future.wait(); + + std::cout << "rendering done!!" << std::endl; + + uint8_t pixels[width * height * 3] = {}; + int i = 0; + for (int32_t y = height - 1; y >= 0; y--) { + for (int32_t x = 0; x < width; x++) { + const glm::ivec4 c = colors.get(x, y); + pixels[i++] = c.r; + pixels[i++] = c.g; + pixels[i++] = c.b; + } + } + + if(stbi_write_png("output.png", width, height, 3, pixels, width * 3) != 1) + return -1; + + return 0; +}