From 4902ebcd5c8673249a86460732361718344bf1af Mon Sep 17 00:00:00 2001 From: "Arthur Brainville (Ybalrid)" Date: Sun, 18 Feb 2018 19:15:50 +0100 Subject: [PATCH] Add the current code of nanort + nanosg This implementation will be based on commit b31dc0bd92eb7ac896079849a2205fac4abbd087 from the nanort project Signed-off by: Arthur Brainville (Ybalrid) --- examples/raytrace/README.md | 130 +++ examples/raytrace/config.json | 9 + examples/raytrace/images/nanosg-demo.png | Bin 0 -> 124517 bytes examples/raytrace/imgui.ini | 25 + examples/raytrace/main.cc | 1032 ++++++++++++++++++++++ examples/raytrace/material.h | 75 ++ examples/raytrace/matrix.cc | 216 +++++ examples/raytrace/mesh.h | 188 ++++ examples/raytrace/nanosg.h | 876 ++++++++++++++++++ examples/raytrace/obj-loader.cc | 459 ++++++++++ examples/raytrace/obj-loader.h | 19 + examples/raytrace/premake5.lua | 114 +++ examples/raytrace/render-config.cc | 116 +++ examples/raytrace/render-config.h | 42 + examples/raytrace/render.cc | 560 ++++++++++++ examples/raytrace/render.h | 45 + examples/raytrace/viwewer.make | 339 +++++++ 17 files changed, 4245 insertions(+) create mode 100644 examples/raytrace/README.md create mode 100644 examples/raytrace/config.json create mode 100644 examples/raytrace/images/nanosg-demo.png create mode 100644 examples/raytrace/imgui.ini create mode 100644 examples/raytrace/main.cc create mode 100644 examples/raytrace/material.h create mode 100644 examples/raytrace/matrix.cc create mode 100644 examples/raytrace/mesh.h create mode 100644 examples/raytrace/nanosg.h create mode 100644 examples/raytrace/obj-loader.cc create mode 100644 examples/raytrace/obj-loader.h create mode 100644 examples/raytrace/premake5.lua create mode 100644 examples/raytrace/render-config.cc create mode 100644 examples/raytrace/render-config.h create mode 100644 examples/raytrace/render.cc create mode 100644 examples/raytrace/render.h create mode 100644 examples/raytrace/viwewer.make diff --git a/examples/raytrace/README.md b/examples/raytrace/README.md new file mode 100644 index 0000000..bfe2e7f --- /dev/null +++ b/examples/raytrace/README.md @@ -0,0 +1,130 @@ +# NanoSG + +Simple, minimal and header-only scene graph library for NanoRT. + +NanoSG itself shoud be compiled with C++-03 compiler, but demo code uses C++11 features. + +![](images/nanosg-demo.png) + +![](https://media.giphy.com/media/l3JDO29fMFndyObHW/giphy.gif) + +## Build + +### Linux or macOS + +``` +$ premake5 gmake +$ make +``` + +### Windows + +``` +$ premake5 vs2015 +``` + +## Data structure + +### Node + +Node represents scene graph node. Tansformation node or Mesh(shape) node. +Node is interpreted as transformation node when passing `nullptr` to Node class constructure. + +Node can contain multiple children. + +### Scene + +Scene contains root nodes and provides the method to find an intersection of nodes. + +## User defined data structure + +Following are required in user application. + +### Mesh class + +Current example code assumes mesh is all composed of triangle meshes. + +Following method must be implemented for `Scene::Traversal`. + +``` +/// +/// Get the geometric normal and the shading normal at `face_idx' th face. +/// +template +void GetNormal(T Ng[3], T Ns[3], const unsigned int face_idx, const T u, const T v) const; +``` + +### Intersection class + +Represents intersection(hit) information. + +### Transform + +Transformation is done in the following procedure. + +`M' = parent_xform x local_xform x local_pivot` + +## Memory management + +`Scene` and `Node` does not create a copy of asset data(e.g. vertices, indices). Thus user must care about memory management of scene assets in user side. + +## API + +API is still subject to change. + +### Node + +``` +void Node::SetName(const std::string &name); +``` + +Set (unique) name for the node. + +``` +void Node::AddChild(const type &child); +``` + +Add node as child node. + +``` +void Node::SetLocalXform(const T xform[4][4]) { +``` + +Set local transformation matrix. Default is identity matrix. + +### Scene + +``` +bool Scene::AddNode(const Node &node); +``` + +Add a node to the scene. + +``` +bool Scene::Commit() { +``` + +Commit the scene. After adding nodes to the scene or changed transformation matrix, call this `Commit` before tracing rays. +`Commit` triggers BVH build in each nodes and updates node's transformation matrix. + +``` +template +bool Scene::Traverse(nanort::Ray &ray, H *isect, const bool cull_back_face = false) const; +``` + +Trace ray into the scene and find an intersection. +Returns `true` when there is an intersection and hit information is stored in `isect`. + +## TODO + +* [ ] Compute pivot point of each node(mesh). + +## Third party libraries and its icenses. + +* picojson : BSD license. +* bt3gui : zlib license. +* glew : BSD/MIT license. +* tinyobjloader : MIT license. +* glm : The Happy Bunny License (Modified MIT License). Copyright (c) 2005 - 2017 G-Truc Creation +* ImGui : The MIT License (MIT). Copyright (c) 2014-2015 Omar Cornut and ImGui contributors +* ImGuizmo : The MIT License (MIT). Copyright (c) 2016 Cedric Guillemet diff --git a/examples/raytrace/config.json b/examples/raytrace/config.json new file mode 100644 index 0000000..b64a131 --- /dev/null +++ b/examples/raytrace/config.json @@ -0,0 +1,9 @@ +{ "obj_filename" : "../common/cornellbox_suzanne.obj", + "scene_scale" : 1.0, + "width" : 512, + "height" : 512, + "eye" : [0, 2.5, 15], + "up" : [0, 1, 0], + "look_at" : [0, 0, 0], + "dummy" : 0 +} diff --git a/examples/raytrace/images/nanosg-demo.png b/examples/raytrace/images/nanosg-demo.png new file mode 100644 index 0000000000000000000000000000000000000000..5715e5826d486c7edde719c8d22faeee27bdc59b GIT binary patch literal 124517 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

dBKaK27f3##_H9IUA zpa^nDWVs8Eq-YQP<<^fT7d)!H;L+_k zv4xK=xHbEzln63}1w)#V7wswtOomBHWM23(OyU%C6wJp4Q#oZd_(UU<10S2=7yKZf z$WM5X>vIkcIyyYvA-TP3_NcCgcZqgyY`DJ#98^8TSNC|;+UrXWGh;7N?k_32oO1K# zvK^(%2bX_8?!1V&DC-jTDIH7M<`r1flPe#5mhoTPChm9e(z2Mx22>u0j*yNzZff$` zX>MiP-rP8igvt1ds~22k-WiN{<&X4E4~b5@(~%VzCsL++GPtSIGFRzP=m^ZzSkknk znyC%rL-vsf*|7*+X_w*brrflqGiVG0GZs$>q4Z8q^}1?J1UF55b1#+V!+*-5SWY)j zkG(iL-7Y&(;$E60GvSdx!Om?l8R|MXEbBZq=sw*!-Pap7bzPU(l*Aa6emC$8Dv4r`LRHus3LuS+a=@<(NugT^ zv2W~^2!?Q{;6;T53av_yQB-}(8K8)#_?^SnZN!sc2%K<{bGtv!F&1W=I$^Oi+qUIy zx!M{OYt}MbGzZ+|hrV>KAHU(;s~=!eIWPxiE_#2v#^#F#Zr(FTmr{M2Un<;X34 zlwz$`B!ONJ*LJN7&|qZN0vYQR^?rW^GJdt_ycA8wYeB~AuEmLwlaMaEu7+Q^-eH>v z5?hQo$)0d+{qiGm7=~?Ym~lcab|a@ic3)6?!jQo-EL+WtTmDl0E=3dG_*9k⪙p6 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 z%0D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 literal 0 HcmV?d00001 diff --git a/examples/raytrace/imgui.ini b/examples/raytrace/imgui.ini new file mode 100644 index 0000000..d72fd2f --- /dev/null +++ b/examples/raytrace/imgui.ini @@ -0,0 +1,25 @@ +[Debug] +Pos=60,60 +Size=400,400 +Collapsed=0 + +[UI] +Pos=-41,636 +Size=469,191 +Collapsed=0 + +[Render] +Pos=583,717 +Size=410,425 +Collapsed=0 + +[Scene] +Pos=60,60 +Size=256,206 +Collapsed=0 + +[Transform] +Pos=579,636 +Size=235,167 +Collapsed=0 + diff --git a/examples/raytrace/main.cc b/examples/raytrace/main.cc new file mode 100644 index 0000000..5aea00b --- /dev/null +++ b/examples/raytrace/main.cc @@ -0,0 +1,1032 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 - 2016 Light Transport Entertainment, Inc. + +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. +*/ + +#ifdef _MSC_VER +#pragma warning(disable : 4244) +#endif + +#define USE_OPENGL2 +#include "OpenGLWindow/OpenGLInclude.h" +#ifdef _WIN32 +#include "OpenGLWindow/Win32OpenGLWindow.h" +#elif defined __APPLE__ +#include "OpenGLWindow/MacOpenGLWindow.h" +#else +// assume linux +#include "OpenGLWindow/X11OpenGLWindow.h" +#endif + +#ifdef _WIN32 +#include +#include +#include +#else +#include +#include +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // C++11 +#include // C++11 +#include // C++11 +#include // C++11 + +#include "imgui.h" +#include "imgui_impl_btgui.h" + +#include "ImGuizmo.h" + +#if defined(_MSC_VER) +#pragma warning(push) +#pragma warning(disable: 4201) +#endif + +#include "glm/mat4x4.hpp" +#include "glm/gtc/quaternion.hpp" +#include "glm/gtc/matrix_transform.hpp" +#include "glm/gtc/type_ptr.hpp" + +#if defined(_MSC_VER) +#pragma warning(pop) +#endif + +#include "nanosg.h" +#include "render-config.h" +#include "render.h" +#include "obj-loader.h" +#include "trackball.h" + +#ifdef WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +b3gDefaultOpenGLWindow* window = 0; +int gWidth = 512; +int gHeight = 512; +int gMousePosX = -1, gMousePosY = -1; +bool gMouseLeftDown = false; + +//FIX issue when max passes is done - no modes is switched. pass must be set to 0 when mode is changed +int gShowBufferMode_prv = SHOW_BUFFER_COLOR; +int gShowBufferMode = SHOW_BUFFER_COLOR; + +bool gTabPressed = false; +bool gShiftPressed = false; +float gShowPositionScale = 1.0f; +float gShowDepthRange[2] = {10.0f, 20.f}; +bool gShowDepthPeseudoColor = true; +float gCurrQuat[4] = {0.0f, 0.0f, 0.0f, 0.0f}; +float gPrevQuat[4] = {0.0f, 0.0f, 0.0f, 0.0f}; + +static nanosg::Scene > gScene; +static example::Asset gAsset; +static std::vector > > gNodes; + +std::atomic gRenderQuit; +std::atomic gRenderRefresh; +std::atomic gRenderCancel; +std::atomic gSceneDirty; +example::RenderConfig gRenderConfig; +std::mutex gMutex; + +struct RenderLayer +{ + std::vector displayRGBA; // Accumurated image. + std::vector rgba; + std::vector auxRGBA; // Auxiliary buffer + std::vector sampleCounts; // Sample num counter for each pixel. + std::vector normalRGBA; // For visualizing normal + std::vector positionRGBA; // For visualizing position + std::vector depthRGBA; // For visualizing depth + std::vector texCoordRGBA; // For visualizing texcoord + std::vector varyCoordRGBA; // For visualizing varycentric coord +}; + +RenderLayer gRenderLayer; + +struct Camera +{ + glm::mat4 view; + glm::mat4 projection; +}; + +struct ManipConfig +{ + glm::vec3 snapTranslation; + glm::vec3 snapRotation; + glm::vec3 snapScale; +}; + +void RequestRender() { + { + std::lock_guard guard(gMutex); + gRenderConfig.pass = 0; + } + + gRenderRefresh = true; + gRenderCancel = true; +} + +void RenderThread() { + { + std::lock_guard guard(gMutex); + gRenderConfig.pass = 0; + } + + while (1) { + if (gRenderQuit) return; + + if (!gRenderRefresh || gRenderConfig.pass >= gRenderConfig.max_passes) { + // Give some cycles to this thread. + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + continue; + } + + auto startT = std::chrono::system_clock::now(); + + // Initialize display buffer for the first pass. + bool initial_pass = false; + { + std::lock_guard guard(gMutex); + if (gRenderConfig.pass == 0) { + initial_pass = true; + } + } + + if (gSceneDirty) { + gScene.Commit(); + gSceneDirty = false; + } + + gRenderCancel = false; + // gRenderCancel may be set to true in main loop. + // Render() will repeatedly check this flag inside the rendering loop. + + bool ret = + example::Renderer::Render(&gRenderLayer.rgba.at(0), &gRenderLayer.auxRGBA.at(0), &gRenderLayer.sampleCounts.at(0), + gCurrQuat, gScene, gAsset, gRenderConfig, gRenderCancel + , + gShowBufferMode //added mode passing + ); + + if (ret) { + std::lock_guard guard(gMutex); + + gRenderConfig.pass++; + } + + auto endT = std::chrono::system_clock::now(); + + std::chrono::duration ms = endT - startT; + + // std::cout << ms.count() << " [ms]\n"; + } +} + +void InitRender(example::RenderConfig* rc) { + rc->pass = 0; + + rc->max_passes = 128; + + gRenderLayer.sampleCounts.resize(rc->width * rc->height); + std::fill(gRenderLayer.sampleCounts.begin(), gRenderLayer.sampleCounts.end(), 0.0); + + gRenderLayer.displayRGBA.resize(rc->width * rc->height * 4); + std::fill(gRenderLayer.displayRGBA.begin(), gRenderLayer.displayRGBA.end(), 0.0); + + gRenderLayer.rgba.resize(rc->width * rc->height * 4); + std::fill(gRenderLayer.rgba.begin(), gRenderLayer.rgba.end(), 0.0); + + gRenderLayer.auxRGBA.resize(rc->width * rc->height * 4); + std::fill(gRenderLayer.auxRGBA.begin(), gRenderLayer.auxRGBA.end(), 0.0); + + gRenderLayer.normalRGBA.resize(rc->width * rc->height * 4); + std::fill(gRenderLayer.normalRGBA.begin(), gRenderLayer.normalRGBA.end(), 0.0); + + gRenderLayer.positionRGBA.resize(rc->width * rc->height * 4); + std::fill(gRenderLayer.positionRGBA.begin(), gRenderLayer.positionRGBA.end(), 0.0); + + gRenderLayer.depthRGBA.resize(rc->width * rc->height * 4); + std::fill(gRenderLayer.depthRGBA.begin(), gRenderLayer.depthRGBA.end(), 0.0); + + gRenderLayer.texCoordRGBA.resize(rc->width * rc->height * 4); + std::fill(gRenderLayer.texCoordRGBA.begin(), gRenderLayer.texCoordRGBA.end(), 0.0); + + gRenderLayer.varyCoordRGBA.resize(rc->width * rc->height * 4); + std::fill(gRenderLayer.varyCoordRGBA.begin(), gRenderLayer.varyCoordRGBA.end(), 0.0); + + rc->normalImage = &gRenderLayer.normalRGBA.at(0); + rc->positionImage = &gRenderLayer.positionRGBA.at(0); + rc->depthImage = &gRenderLayer.depthRGBA.at(0); + rc->texcoordImage = &gRenderLayer.texCoordRGBA.at(0); + rc->varycoordImage = &gRenderLayer.varyCoordRGBA.at(0); + + trackball(gCurrQuat, 0.0f, 0.0f, 0.0f, 0.0f); +} + +void checkErrors(std::string desc) { + GLenum e = glGetError(); + if (e != GL_NO_ERROR) { + fprintf(stderr, "OpenGL error in \"%s\": %d (%d)\n", desc.c_str(), e, e); + exit(20); + } +} + + +static int CreateDisplayTextureGL(const float *data, int width, + int height, int components) { + GLuint id; + glGenTextures(1, &id); + + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + + glBindTexture(GL_TEXTURE_2D, id); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + + GLenum format = GL_RGBA; + if (components == 1) { + format = GL_LUMINANCE; + } else if (components == 2) { + format = GL_LUMINANCE_ALPHA; + } else if (components == 3) { + format = GL_RGB; + } else if (components == 4) { + format = GL_RGBA; + } else { + assert(0); // "Invalid components" + } + + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, width, height, 0, format, + GL_FLOAT, data); + + glBindTexture(GL_TEXTURE_2D, last_texture); + + return static_cast(id); +} + +void keyboardCallback(int keycode, int state) { + printf("hello key %d, state %d(ctrl %d)\n", keycode, state, + window->isModifierKeyPressed(B3G_CONTROL)); + // if (keycode == 'q' && window && window->isModifierKeyPressed(B3G_SHIFT)) { + if (keycode == 27) { + if (window) window->setRequestExit(); + } else if (keycode == ' ') { + // reset. + trackball(gCurrQuat, 0.0f, 0.0f, 0.0f, 0.0f); + // clear buffer. + memset(gRenderLayer.rgba.data(), 0, sizeof(float) * gRenderConfig.width * gRenderConfig.height * 4); + memset(gRenderLayer.sampleCounts.data(), 0, sizeof(int) * gRenderConfig.width * gRenderConfig.height); + } else if (keycode == 9) { + gTabPressed = (state == 1); + } else if (keycode == B3G_SHIFT) { + gShiftPressed = (state == 1); + } + + ImGui_ImplBtGui_SetKeyState(keycode, (state == 1)); + + if (keycode >= 32 && keycode <= 126) { + if (state == 1) { + ImGui_ImplBtGui_SetChar(keycode); + } + } +} + +void mouseMoveCallback(float x, float y) { + + if (gMouseLeftDown) { + if (ImGuizmo::IsOver() || ImGuizmo::IsUsing()) { + } else { + float w = static_cast(gRenderConfig.width); + float h = static_cast(gRenderConfig.height); + + float y_offset = gHeight - h; + + if (gTabPressed) { + const float dolly_scale = 0.1f; + gRenderConfig.eye[2] += dolly_scale * (gMousePosY - y); + gRenderConfig.look_at[2] += dolly_scale * (gMousePosY - y); + } else if (gShiftPressed) { + const float trans_scale = 0.02f; + gRenderConfig.eye[0] += trans_scale * (gMousePosX - x); + gRenderConfig.eye[1] -= trans_scale * (gMousePosY - y); + gRenderConfig.look_at[0] += trans_scale * (gMousePosX - x); + gRenderConfig.look_at[1] -= trans_scale * (gMousePosY - y); + + } else { + // Adjust y. + trackball(gPrevQuat, (2.f * gMousePosX - w) / (float)w, + (h - 2.f * (gMousePosY - y_offset)) / (float)h, + (2.f * x - w) / (float)w, + (h - 2.f * (y - y_offset)) / (float)h); + add_quats(gPrevQuat, gCurrQuat, gCurrQuat); + } + RequestRender(); + } + } + + gMousePosX = (int)x; + gMousePosY = (int)y; +} + +void mouseButtonCallback(int button, int state, float x, float y) { + (void)x; + (void)y; + ImGui_ImplBtGui_SetMouseButtonState(button, (state == 1)); + + ImGuiIO& io = ImGui::GetIO(); + if (io.WantCaptureMouse || io.WantCaptureKeyboard) { + return; + } + + // left button + if (button == 0) { + if (state) { + gMouseLeftDown = true; + if (ImGuizmo::IsOver() || ImGuizmo::IsUsing()) { + } else { + trackball(gPrevQuat, 0.0f, 0.0f, 0.0f, 0.0f); + } + } else { + gMouseLeftDown = false; + if (ImGuizmo::IsOver() || ImGuizmo::IsUsing()) { + gSceneDirty = true; + RequestRender(); + } + } + } +} + +void resizeCallback(float width, float height) { + //GLfloat h = (GLfloat)height / (GLfloat)width; + GLfloat xmax, znear, zfar; + + znear = 1.0f; + zfar = 1000.0f; + xmax = znear * 0.5f; + + gWidth = static_cast(width); + gHeight = static_cast(height); +} + +inline float pesudoColor(float v, int ch) { + if (ch == 0) { // red + if (v <= 0.5f) + return 0.f; + else if (v < 0.75f) + return (v - 0.5f) / 0.25f; + else + return 1.f; + } else if (ch == 1) { // green + if (v <= 0.25f) + return v / 0.25f; + else if (v < 0.75f) + return 1.f; + else + return 1.f - (v - 0.75f) / 0.25f; + } else if (ch == 2) { // blue + if (v <= 0.25f) + return 1.f; + else if (v < 0.5f) + return 1.f - (v - 0.25f) / 0.25f; + else + return 0.f; + } else { // alpha + return 1.f; + } +} + +void UpdateDisplayTextureGL(GLint tex_id, int width, int height) { + if (tex_id < 0) { + // ??? + return; + } + + std::vector buf; + buf.resize(width * height * 4); + + if (gShowBufferMode == SHOW_BUFFER_COLOR) { + // normalize + for (size_t i = 0; i < buf.size() / 4; i++) { + buf[4 * i + 0] = gRenderLayer.rgba[4 * i + 0]; + buf[4 * i + 1] = gRenderLayer.rgba[4 * i + 1]; + buf[4 * i + 2] = gRenderLayer.rgba[4 * i + 2]; + buf[4 * i + 3] = gRenderLayer.rgba[4 * i + 3]; + if (gRenderLayer.sampleCounts[i] > 0) { + buf[4 * i + 0] /= static_cast(gRenderLayer.sampleCounts[i]); + buf[4 * i + 1] /= static_cast(gRenderLayer.sampleCounts[i]); + buf[4 * i + 2] /= static_cast(gRenderLayer.sampleCounts[i]); + buf[4 * i + 3] /= static_cast(gRenderLayer.sampleCounts[i]); + } + } + } else if (gShowBufferMode == SHOW_BUFFER_NORMAL) { + for (size_t i = 0; i < buf.size(); i++) { + buf[i] = gRenderLayer.normalRGBA[i]; + } + } else if (gShowBufferMode == SHOW_BUFFER_POSITION) { + for (size_t i = 0; i < buf.size(); i++) { + buf[i] = gRenderLayer.positionRGBA[i] * gShowPositionScale; + } + } else if (gShowBufferMode == SHOW_BUFFER_DEPTH) { + float d_min = std::min(gShowDepthRange[0], gShowDepthRange[1]); + float d_diff = fabsf(gShowDepthRange[1] - gShowDepthRange[0]); + d_diff = std::max(d_diff, std::numeric_limits::epsilon()); + for (size_t i = 0; i < buf.size(); i++) { + float v = (gRenderLayer.depthRGBA[i] - d_min) / d_diff; + if (gShowDepthPeseudoColor) { + buf[i] = pesudoColor(v, i % 4); + } else { + buf[i] = v; + } + } + } else if (gShowBufferMode == SHOW_BUFFER_TEXCOORD) { + for (size_t i = 0; i < buf.size(); i++) { + buf[i] = gRenderLayer.texCoordRGBA[i]; + } + } else if (gShowBufferMode == SHOW_BUFFER_VARYCOORD) { + for (size_t i = 0; i < buf.size(); i++) { + buf[i] = gRenderLayer.varyCoordRGBA[i]; + } + } + + // Flip Y + std::vector disp; + disp.resize(width * height * 4); + { + for (size_t y = 0; y < height; y++) { + memcpy(&disp[4 * (y * width)], &buf[4 * ((height - y - 1) * width)], sizeof(float) * 4 * width); + } + + } + + glBindTexture(GL_TEXTURE_2D, tex_id); + glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_RGBA, GL_FLOAT, disp.data()); + glBindTexture(GL_TEXTURE_2D, 0); + + //glRasterPos2i(-1, -1); + //glDrawPixels(width, height, GL_RGBA, GL_FLOAT, + // static_cast(&buf.at(0))); +} + +void EditTransform(const ManipConfig &config, const Camera& camera, glm::mat4& matrix) +{ + static ImGuizmo::OPERATION mCurrentGizmoOperation(ImGuizmo::ROTATE); + static ImGuizmo::MODE mCurrentGizmoMode(ImGuizmo::WORLD); + if (ImGui::IsKeyPressed(90)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + if (ImGui::IsKeyPressed(69)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + if (ImGui::IsKeyPressed(82)) // r Key + mCurrentGizmoOperation = ImGuizmo::SCALE; + if (ImGui::RadioButton("Translate", mCurrentGizmoOperation == ImGuizmo::TRANSLATE)) + mCurrentGizmoOperation = ImGuizmo::TRANSLATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Rotate", mCurrentGizmoOperation == ImGuizmo::ROTATE)) + mCurrentGizmoOperation = ImGuizmo::ROTATE; + ImGui::SameLine(); + if (ImGui::RadioButton("Scale", mCurrentGizmoOperation == ImGuizmo::SCALE)) + mCurrentGizmoOperation = ImGuizmo::SCALE; + float matrixTranslation[3], matrixRotation[3], matrixScale[3]; + ImGuizmo::DecomposeMatrixToComponents(&matrix[0][0], matrixTranslation, matrixRotation, matrixScale); + ImGui::InputFloat3("Tr", matrixTranslation, 3); + ImGui::InputFloat3("Rt", matrixRotation, 3); + ImGui::InputFloat3("Sc", matrixScale, 3); + ImGuizmo::RecomposeMatrixFromComponents(matrixTranslation, matrixRotation, matrixScale, &matrix[0][0]); + + if (mCurrentGizmoOperation != ImGuizmo::SCALE) + { + if (ImGui::RadioButton("Local", mCurrentGizmoMode == ImGuizmo::LOCAL)) + mCurrentGizmoMode = ImGuizmo::LOCAL; + ImGui::SameLine(); + if (ImGui::RadioButton("World", mCurrentGizmoMode == ImGuizmo::WORLD)) + mCurrentGizmoMode = ImGuizmo::WORLD; + } + static bool useSnap(false); + if (ImGui::IsKeyPressed(83)) + useSnap = !useSnap; + ImGui::Checkbox("", &useSnap); + ImGui::SameLine(); + glm::vec3 snap; + switch (mCurrentGizmoOperation) + { + case ImGuizmo::TRANSLATE: + snap = config.snapTranslation; + ImGui::InputFloat3("Snap", &snap.x); + break; + case ImGuizmo::ROTATE: + snap = config.snapRotation; + ImGui::InputFloat("Angle Snap", &snap.x); + break; + case ImGuizmo::SCALE: + snap = config.snapScale; + ImGui::InputFloat("Scale Snap", &snap.x); + break; + } + ImGuiIO& io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + ImGuizmo::Manipulate(&camera.view[0][0], &camera.projection[0][0], mCurrentGizmoOperation, mCurrentGizmoMode, &matrix[0][0], NULL, useSnap ? &snap.x : NULL); +} + +void DrawMesh(const example::Mesh *mesh) +{ + // TODO(LTE): Use vertex array or use display list. + + glBegin(GL_TRIANGLES); + + if (!mesh->facevarying_normals.empty()) { + for (size_t i = 0; i < mesh->faces.size() / 3; i++) { + unsigned int f0 = mesh->faces[3 * i + 0]; + unsigned int f1 = mesh->faces[3 * i + 1]; + unsigned int f2 = mesh->faces[3 * i + 2]; + + glNormal3f(mesh->facevarying_normals[9 * i + 0], + mesh->facevarying_normals[9 * i + 1], + mesh->facevarying_normals[9 * i + 2]); + glVertex3f(mesh->vertices[3 * f0 + 0], + mesh->vertices[3 * f0 + 1], + mesh->vertices[3 * f0 + 2]); + glNormal3f(mesh->facevarying_normals[9 * i + 3], + mesh->facevarying_normals[9 * i + 4], + mesh->facevarying_normals[9 * i + 5]); + glVertex3f(mesh->vertices[3 * f1 + 0], + mesh->vertices[3 * f1 + 1], + mesh->vertices[3 * f1 + 2]); + glNormal3f(mesh->facevarying_normals[9 * i + 6], + mesh->facevarying_normals[9 * i + 7], + mesh->facevarying_normals[9 * i + 8]); + glVertex3f(mesh->vertices[3 * f2 + 0], + mesh->vertices[3 * f2 + 1], + mesh->vertices[3 * f2 + 2]); + } + + } else { + for (size_t i = 0; i < mesh->faces.size() / 3; i++) { + unsigned int f0 = mesh->faces[3 * i + 0]; + unsigned int f1 = mesh->faces[3 * i + 1]; + unsigned int f2 = mesh->faces[3 * i + 2]; + + glVertex3f(mesh->vertices[3 * f0 + 0], + mesh->vertices[3 * f0 + 1], + mesh->vertices[3 * f0 + 2]); + glVertex3f(mesh->vertices[3 * f1 + 0], + mesh->vertices[3 * f1 + 1], + mesh->vertices[3 * f1 + 2]); + glVertex3f(mesh->vertices[3 * f2 + 0], + mesh->vertices[3 * f2 + 1], + mesh->vertices[3 * f2 + 2]); + } + } + + glEnd(); +} + +void DrawNode(const nanosg::Node > &node) +{ + glPushMatrix(); + glMultMatrixf(node.GetLocalXformPtr()); + + if (node.GetMesh()) { + DrawMesh(node.GetMesh()); + } + + for (size_t i = 0; i < node.GetChildren().size(); i++) { + DrawNode(node.GetChildren()[i]); + } + + glPopMatrix(); +} + +// Draw scene with OpenGL +void DrawScene(const nanosg::Scene > &scene, const Camera &camera) +{ + glEnable(GL_DEPTH_TEST); + + glEnable(GL_LIGHTING); + glEnable(GL_LIGHT0); + glEnable(GL_LIGHT1); + + // FIXME(LTE): Use scene bounding box. + const float light0_pos[4] = {1000.0f, 1000.0f, 1000.0f, 0.0f}; + const float light1_pos[4] = {-1000.0f, -1000.0f, -1000.0f, 0.0f}; + + const float light_diffuse[4] = {0.5f, 0.5f, 0.5f, 1.0f}; + + glLightfv(GL_LIGHT0, GL_POSITION, &light0_pos[0]); + glLightfv(GL_LIGHT0, GL_DIFFUSE, &light_diffuse[0]); + glLightfv(GL_LIGHT1, GL_POSITION, &light1_pos[0]); + glLightfv(GL_LIGHT1, GL_DIFFUSE, &light_diffuse[0]); + + const std::vector > > &root_nodes = scene.GetNodes(); + + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadMatrixf(&camera.projection[0][0]); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadMatrixf(&camera.view[0][0]); + + for (size_t i = 0; i < root_nodes.size(); i++) { + DrawNode(root_nodes[i]); + } + + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + + glDisable(GL_LIGHT0); + glDisable(GL_LIGHT1); + glDisable(GL_LIGHTING); + glDisable(GL_DEPTH_TEST); + +} + +void BuildSceneItems( + std::vector *display_names, + std::vector *names, + const nanosg::Node > &node, + int indent) +{ + if (node.GetName().empty()) { + // Skip a node with empty name. + return; + } + + std::stringstream ss; + for (int i = 0; i < indent; i++) { + ss << " "; + } + + ss << node.GetName(); + std::string display_name = ss.str(); + + display_names->push_back(display_name); + names->push_back(node.GetName()); + + for (size_t i = 0; i < node.GetChildren().size(); i++) { + BuildSceneItems(display_names, names, node.GetChildren()[i], indent + 1); + } + +} + +//tigra: add default material + example::Material default_material; + +int main(int argc, char** argv) { + std::string config_filename = "config.json"; + + if (argc > 1) { + config_filename = argv[1]; + } + + // load config + { + bool ret = + example::LoadRenderConfig(&gRenderConfig, config_filename.c_str()); + if (!ret) { + std::cerr << "Failed to load [ " << config_filename << " ]" << std::endl; + return -1; + } + } + + // construct the scene + { + std::vector > meshes; + std::vector materials; + std::vector textures; + + //tigra: set default material to 95% white diffuse + default_material.diffuse[0] = 0.95f; + default_material.diffuse[1] = 0.95f; + default_material.diffuse[2] = 0.95f; + + default_material.specular[0] = 0; + default_material.specular[1] = 0; + default_material.specular[2] = 0; + + bool ret = LoadObj(gRenderConfig.obj_filename, gRenderConfig.scene_scale, &meshes, &materials, &textures); + if (!ret) { + std::cerr << "Failed to load .obj [ " << gRenderConfig.obj_filename << " ]" << std::endl; + return -1; + } + + gAsset.materials = materials; + gAsset.default_material = default_material; + gAsset.textures = textures; + + for (size_t n = 0; n < meshes.size(); n++) { + size_t mesh_id = gAsset.meshes.size(); + gAsset.meshes.push_back(meshes[mesh_id]); + } + + for (size_t n = 0; n < gAsset.meshes.size(); n++) { + + nanosg::Node > node(&gAsset.meshes[n]); + node.SetName(meshes[n].name); + node.SetLocalXform(meshes[n].pivot_xform); // Use mesh's pivot transform as node's local transform. + gNodes.push_back(node); + + gScene.AddNode(node); + } + + if (!gScene.Commit()) { + std::cerr << "Failed to commit the scene." << std::endl; + return -1; + } + + float bmin[3], bmax[3]; + gScene.GetBoundingBox(bmin, bmax); + printf(" # of nodes : %d\n", int(gNodes.size())); + printf(" Scene Bmin : %f, %f, %f\n", bmin[0], bmin[1], bmin[2]); + printf(" Scene Bmax : %f, %f, %f\n", bmax[0], bmax[1], bmax[2]); + + } + + std::vector imgui_node_names; + std::vector display_node_names; + std::vector node_names; + std::map > *> node_map; + + { + for (size_t i = 0; i < gScene.GetNodes().size(); i++) { + BuildSceneItems(&display_node_names, &node_names, gScene.GetNodes()[i], /* indent */0); + } + + // List of strings for imgui. + // Assume nodes in the scene does not change. + for (size_t i = 0; i < display_node_names.size(); i++) { + //std::cout << "name : " << display_node_names[i] << std::endl; + imgui_node_names.push_back(display_node_names[i].c_str()); + } + + // Construct list index <-> Node ptr map. + for (size_t i = 0; i < node_names.size(); i++) { + nanosg::Node > *node; + + if (gScene.FindNode(node_names[i], &node)) { + //std::cout << "id : " << i << ", name : " << node_names[i] << std::endl; + node_map[i] = node; + } + } + } + + + window = new b3gDefaultOpenGLWindow; + b3gWindowConstructionInfo ci; +#ifdef USE_OPENGL2 + ci.m_openglVersion = 2; +#endif + ci.m_width = 1024; + ci.m_height = 800; + window->createWindow(ci); + + window->setWindowTitle("view"); + +#ifndef __APPLE__ +#ifndef _WIN32 + // some Linux implementations need the 'glewExperimental' to be true + glewExperimental = GL_TRUE; +#endif + if (glewInit() != GLEW_OK) { + fprintf(stderr, "Failed to initialize GLEW\n"); + exit(-1); + } + + if (!GLEW_VERSION_2_1) { + fprintf(stderr, "OpenGL 2.1 is not available\n"); + exit(-1); + } +#endif + + InitRender(&gRenderConfig); + + checkErrors("init"); + + window->setMouseButtonCallback(mouseButtonCallback); + window->setMouseMoveCallback(mouseMoveCallback); + checkErrors("mouse"); + window->setKeyboardCallback(keyboardCallback); + checkErrors("keyboard"); + window->setResizeCallback(resizeCallback); + checkErrors("resize"); + + ImGui_ImplBtGui_Init(window); + + ImGuiIO& io = ImGui::GetIO(); + io.Fonts->AddFontDefault(); + //io.Fonts->AddFontFromFileTTF("./DroidSans.ttf", 15.0f); + + glm::mat4 projection(1.0f); + glm::mat4 view(1.0f); + glm::mat4 matrix(1.0f); + + Camera camera; + + std::thread renderThread(RenderThread); + + // Trigger initial rendering request + RequestRender(); + + while (!window->requestedExit()) { + window->startRendering(); + + checkErrors("begin frame"); + + ImGui_ImplBtGui_NewFrame(gMousePosX, gMousePosY); + + ImGuizmo::BeginFrame(); + ImGuizmo::Enable(true); + + //ImGuiIO &io = ImGui::GetIO(); + ImGuizmo::SetRect(0, 0, io.DisplaySize.x, io.DisplaySize.y); + + ImGui::Begin("UI"); + { + static float col[3] = {0, 0, 0}; + static float f = 0.0f; + // if (ImGui::ColorEdit3("color", col)) { + // RequestRender(); + //} + // ImGui::InputFloat("intensity", &f); + if (ImGui::InputFloat3("eye", gRenderConfig.eye)) { + RequestRender(); + } + if (ImGui::InputFloat3("up", gRenderConfig.up)) { + RequestRender(); + } + if (ImGui::InputFloat3("look_at", gRenderConfig.look_at)) { + RequestRender(); + } + + ImGui::RadioButton("color", &gShowBufferMode, SHOW_BUFFER_COLOR); + ImGui::SameLine(); + ImGui::RadioButton("normal", &gShowBufferMode, SHOW_BUFFER_NORMAL); + ImGui::SameLine(); + ImGui::RadioButton("position", &gShowBufferMode, SHOW_BUFFER_POSITION); + ImGui::SameLine(); + ImGui::RadioButton("depth", &gShowBufferMode, SHOW_BUFFER_DEPTH); + ImGui::SameLine(); + ImGui::RadioButton("texcoord", &gShowBufferMode, SHOW_BUFFER_TEXCOORD); + ImGui::SameLine(); + ImGui::RadioButton("varycoord", &gShowBufferMode, SHOW_BUFFER_VARYCOORD); + + ImGui::InputFloat("show pos scale", &gShowPositionScale); + + ImGui::InputFloat2("show depth range", gShowDepthRange); + ImGui::Checkbox("show depth pesudo color", &gShowDepthPeseudoColor); + } + + ImGui::End(); + + glViewport(0, 0, window->getWidth(), window->getHeight()); + glClearColor(0.0f, 0.1f, 0.2f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + + checkErrors("clear"); + + //fix max passes issue + if(gShowBufferMode_prv!=gShowBufferMode) + { + gRenderConfig.pass = 0; + gShowBufferMode_prv = gShowBufferMode; + } + + + // Render display window + { + static GLint gl_texid = -1; + if (gl_texid < 0) { + gl_texid = CreateDisplayTextureGL(NULL, gRenderConfig.width, gRenderConfig.height, 4); + } + + // Refresh texture until rendering finishes. + if (gRenderConfig.pass < gRenderConfig.max_passes) { + // FIXME(LTE): Do not update GL texture frequently. + UpdateDisplayTextureGL(gl_texid, gRenderConfig.width, gRenderConfig.height); + } + + ImGui::Begin("Render"); + ImTextureID tex_id = reinterpret_cast( + static_cast(gl_texid)); + ImGui::Image(tex_id, ImVec2(256, 256), ImVec2(0, 0), + ImVec2(1, 1));// Setup camera and draw imguizomo + + ImGui::End(); + + } + + // scene graph tree + glm::mat4 node_matrix; + static int node_selected = 0; + { + ImGui::Begin("Scene"); + + ImGui::ListBox("Node list", &node_selected, imgui_node_names.data(), imgui_node_names.size(), 16); + node_matrix = glm::make_mat4(node_map[node_selected]->GetLocalXformPtr()); + + ImGui::End(); + } + + { + ImGui::Begin("Transform"); + + static ImGuizmo::OPERATION guizmo_op(ImGuizmo::ROTATE); + static ImGuizmo::MODE guizmo_mode(ImGuizmo::WORLD); + + glm::vec3 eye, lookat, up; + eye[0] = gRenderConfig.eye[0]; + eye[1] = gRenderConfig.eye[1]; + eye[2] = gRenderConfig.eye[2]; + + lookat[0] = gRenderConfig.look_at[0]; + lookat[1] = gRenderConfig.look_at[1]; + lookat[2] = gRenderConfig.look_at[2]; + + up[0] = gRenderConfig.up[0]; + up[1] = gRenderConfig.up[1]; + up[2] = gRenderConfig.up[2]; + + // NOTE(LTE): w, then (x,y,z) for glm::quat. + glm::quat rot = glm::quat(gCurrQuat[3], gCurrQuat[0], gCurrQuat[1], gCurrQuat[2]); + glm::mat4 rm = glm::mat4_cast(rot); + + view = glm::lookAt(eye, lookat, up) * glm::inverse(glm::mat4_cast(rot)); + projection = glm::perspective (45.0f, float(window->getWidth()) / float(window->getHeight()), 0.01f, 1000.0f); + + camera.view = view; + camera.projection = projection; + ManipConfig manip_config; + + EditTransform(manip_config, camera, node_matrix); + + float mat[4][4]; + memcpy(mat, &node_matrix[0][0], sizeof(float) * 16); + node_map[node_selected]->SetLocalXform(mat); + + checkErrors("edit_transform"); + + ImGui::End(); + } + + // Draw scene in OpenGL + DrawScene(gScene, camera); + + // Draw imgui + ImGui::Render(); + + checkErrors("im render"); + + window->endRendering(); + + // Give some cycles to this thread. + std::this_thread::sleep_for(std::chrono::milliseconds(16)); + } + + { + gRenderCancel = true; + gRenderQuit = true; + renderThread.join(); + } + + ImGui_ImplBtGui_Shutdown(); + delete window; + + return EXIT_SUCCESS; +} diff --git a/examples/raytrace/material.h b/examples/raytrace/material.h new file mode 100644 index 0000000..c11b953 --- /dev/null +++ b/examples/raytrace/material.h @@ -0,0 +1,75 @@ +#ifndef EXAMPLE_MATERIAL_H_ +#define EXAMPLE_MATERIAL_H_ + +#include + +#ifdef __clang__ +#pragma clang diagnostic push +#if __has_warning("-Wzero-as-null-pointer-constant") +#pragma clang diagnostic ignored "-Wzero-as-null-pointer-constant" +#endif +#endif + +namespace example { + +struct Material { + // float ambient[3]; + float diffuse[3]; + float specular[3]; + // float reflection[3]; + // float refraction[3]; + int id; + int diffuse_texid; + int specular_texid; + // int reflection_texid; + // int transparency_texid; + // int bump_texid; + // int normal_texid; // normal map + // int alpha_texid; // alpha map + + Material() { + // ambient[0] = 0.0; + // ambient[1] = 0.0; + // ambient[2] = 0.0; + diffuse[0] = 0.5; + diffuse[1] = 0.5; + diffuse[2] = 0.5; + specular[0] = 0.5; + specular[1] = 0.5; + specular[2] = 0.5; + // reflection[0] = 0.0; + // reflection[1] = 0.0; + // reflection[2] = 0.0; + // refraction[0] = 0.0; + // refraction[1] = 0.0; + // refraction[2] = 0.0; + id = -1; + diffuse_texid = -1; + specular_texid = -1; + // reflection_texid = -1; + // transparency_texid = -1; + // bump_texid = -1; + // normal_texid = -1; + // alpha_texid = -1; + } +}; + +struct Texture { + int width; + int height; + int components; + int _pad_; + unsigned char* image; + + Texture() { + width = -1; + height = -1; + components = -1; + image = NULL; + } +}; + +} // namespace example + + +#endif // EXAMPLE_MATERIAL_H_ diff --git a/examples/raytrace/matrix.cc b/examples/raytrace/matrix.cc new file mode 100644 index 0000000..b6670a9 --- /dev/null +++ b/examples/raytrace/matrix.cc @@ -0,0 +1,216 @@ +#include +#include + +#include "matrix.h" + +//using namespace mallie; + +static inline float vdot(float a[3], float b[3]) { + return a[0] * b[0] + a[1] * b[1] + a[2] * b[2]; +} + +static inline void vcross(float c[3], float a[3], float b[3]) { + c[0] = a[1] * b[2] - a[2] * b[1]; + c[1] = a[2] * b[0] - a[0] * b[2]; + c[2] = a[0] * b[1] - a[1] * b[0]; +} + +static inline float vlength(float v[3]) { + float len2 = vdot(v, v); + if (std::abs(len2) > 1.0e-30) { + return sqrt(len2); + } + return 0.0f; +} + +static void vnormalize(float v[3]) { + float len = vlength(v); + if (std::abs(len) > 1.0e-30) { + float inv_len = 1.0f / len; + v[0] *= inv_len; + v[1] *= inv_len; + v[2] *= inv_len; + } +} + +void Matrix::Print(float m[4][4]) { + for (int i = 0; i < 4; i++) { + printf("m[%d] = %f, %f, %f, %f\n", i, m[i][0], m[i][1], m[i][2], m[i][3]); + } +} + +void Matrix::LookAt(float m[4][4], float eye[3], float lookat[3], + float up[3]) { + + float u[3], v[3]; + float look[3]; + look[0] = lookat[0] - eye[0]; + look[1] = lookat[1] - eye[1]; + look[2] = lookat[2] - eye[2]; + vnormalize(look); + + vcross(u, look, up); + vnormalize(u); + + vcross(v, u, look); + vnormalize(v); + +#if 0 + m[0][0] = u[0]; + m[0][1] = v[0]; + m[0][2] = -look[0]; + m[0][3] = 0.0; + + m[1][0] = u[1]; + m[1][1] = v[1]; + m[1][2] = -look[1]; + m[1][3] = 0.0; + + m[2][0] = u[2]; + m[2][1] = v[2]; + m[2][2] = -look[2]; + m[2][3] = 0.0; + + m[3][0] = eye[0]; + m[3][1] = eye[1]; + m[3][2] = eye[2]; + m[3][3] = 1.0; +#else + m[0][0] = u[0]; + m[1][0] = v[0]; + m[2][0] = -look[0]; + m[3][0] = eye[0]; + + m[0][1] = u[1]; + m[1][1] = v[1]; + m[2][1] = -look[1]; + m[3][1] = eye[1]; + + m[0][2] = u[2]; + m[1][2] = v[2]; + m[2][2] = -look[2]; + m[3][2] = eye[2]; + + m[0][3] = 0.0; + m[1][3] = 0.0; + m[2][3] = 0.0; + m[3][3] = 1.0; + +#endif +} + +void Matrix::Inverse(float m[4][4]) { + /* + * codes from intel web + * cramer's rule version + */ + int i, j; + float tmp[12]; /* tmp array for pairs */ + float tsrc[16]; /* array of transpose source matrix */ + float det; /* determinant */ + + /* transpose matrix */ + for (i = 0; i < 4; i++) { + tsrc[i] = m[i][0]; + tsrc[i + 4] = m[i][1]; + tsrc[i + 8] = m[i][2]; + tsrc[i + 12] = m[i][3]; + } + + /* calculate pair for first 8 elements(cofactors) */ + tmp[0] = tsrc[10] * tsrc[15]; + tmp[1] = tsrc[11] * tsrc[14]; + tmp[2] = tsrc[9] * tsrc[15]; + tmp[3] = tsrc[11] * tsrc[13]; + tmp[4] = tsrc[9] * tsrc[14]; + tmp[5] = tsrc[10] * tsrc[13]; + tmp[6] = tsrc[8] * tsrc[15]; + tmp[7] = tsrc[11] * tsrc[12]; + tmp[8] = tsrc[8] * tsrc[14]; + tmp[9] = tsrc[10] * tsrc[12]; + tmp[10] = tsrc[8] * tsrc[13]; + tmp[11] = tsrc[9] * tsrc[12]; + + /* calculate first 8 elements(cofactors) */ + m[0][0] = tmp[0] * tsrc[5] + tmp[3] * tsrc[6] + tmp[4] * tsrc[7]; + m[0][0] -= tmp[1] * tsrc[5] + tmp[2] * tsrc[6] + tmp[5] * tsrc[7]; + m[0][1] = tmp[1] * tsrc[4] + tmp[6] * tsrc[6] + tmp[9] * tsrc[7]; + m[0][1] -= tmp[0] * tsrc[4] + tmp[7] * tsrc[6] + tmp[8] * tsrc[7]; + m[0][2] = tmp[2] * tsrc[4] + tmp[7] * tsrc[5] + tmp[10] * tsrc[7]; + m[0][2] -= tmp[3] * tsrc[4] + tmp[6] * tsrc[5] + tmp[11] * tsrc[7]; + m[0][3] = tmp[5] * tsrc[4] + tmp[8] * tsrc[5] + tmp[11] * tsrc[6]; + m[0][3] -= tmp[4] * tsrc[4] + tmp[9] * tsrc[5] + tmp[10] * tsrc[6]; + m[1][0] = tmp[1] * tsrc[1] + tmp[2] * tsrc[2] + tmp[5] * tsrc[3]; + m[1][0] -= tmp[0] * tsrc[1] + tmp[3] * tsrc[2] + tmp[4] * tsrc[3]; + m[1][1] = tmp[0] * tsrc[0] + tmp[7] * tsrc[2] + tmp[8] * tsrc[3]; + m[1][1] -= tmp[1] * tsrc[0] + tmp[6] * tsrc[2] + tmp[9] * tsrc[3]; + m[1][2] = tmp[3] * tsrc[0] + tmp[6] * tsrc[1] + tmp[11] * tsrc[3]; + m[1][2] -= tmp[2] * tsrc[0] + tmp[7] * tsrc[1] + tmp[10] * tsrc[3]; + m[1][3] = tmp[4] * tsrc[0] + tmp[9] * tsrc[1] + tmp[10] * tsrc[2]; + m[1][3] -= tmp[5] * tsrc[0] + tmp[8] * tsrc[1] + tmp[11] * tsrc[2]; + + /* calculate pairs for second 8 elements(cofactors) */ + tmp[0] = tsrc[2] * tsrc[7]; + tmp[1] = tsrc[3] * tsrc[6]; + tmp[2] = tsrc[1] * tsrc[7]; + tmp[3] = tsrc[3] * tsrc[5]; + tmp[4] = tsrc[1] * tsrc[6]; + tmp[5] = tsrc[2] * tsrc[5]; + tmp[6] = tsrc[0] * tsrc[7]; + tmp[7] = tsrc[3] * tsrc[4]; + tmp[8] = tsrc[0] * tsrc[6]; + tmp[9] = tsrc[2] * tsrc[4]; + tmp[10] = tsrc[0] * tsrc[5]; + tmp[11] = tsrc[1] * tsrc[4]; + + /* calculate second 8 elements(cofactors) */ + m[2][0] = tmp[0] * tsrc[13] + tmp[3] * tsrc[14] + tmp[4] * tsrc[15]; + m[2][0] -= tmp[1] * tsrc[13] + tmp[2] * tsrc[14] + tmp[5] * tsrc[15]; + m[2][1] = tmp[1] * tsrc[12] + tmp[6] * tsrc[14] + tmp[9] * tsrc[15]; + m[2][1] -= tmp[0] * tsrc[12] + tmp[7] * tsrc[14] + tmp[8] * tsrc[15]; + m[2][2] = tmp[2] * tsrc[12] + tmp[7] * tsrc[13] + tmp[10] * tsrc[15]; + m[2][2] -= tmp[3] * tsrc[12] + tmp[6] * tsrc[13] + tmp[11] * tsrc[15]; + m[2][3] = tmp[5] * tsrc[12] + tmp[8] * tsrc[13] + tmp[11] * tsrc[14]; + m[2][3] -= tmp[4] * tsrc[12] + tmp[9] * tsrc[13] + tmp[10] * tsrc[14]; + m[3][0] = tmp[2] * tsrc[10] + tmp[5] * tsrc[11] + tmp[1] * tsrc[9]; + m[3][0] -= tmp[4] * tsrc[11] + tmp[0] * tsrc[9] + tmp[3] * tsrc[10]; + m[3][1] = tmp[8] * tsrc[11] + tmp[0] * tsrc[8] + tmp[7] * tsrc[10]; + m[3][1] -= tmp[6] * tsrc[10] + tmp[9] * tsrc[11] + tmp[1] * tsrc[8]; + m[3][2] = tmp[6] * tsrc[9] + tmp[11] * tsrc[11] + tmp[3] * tsrc[8]; + m[3][2] -= tmp[10] * tsrc[11] + tmp[2] * tsrc[8] + tmp[7] * tsrc[9]; + m[3][3] = tmp[10] * tsrc[10] + tmp[4] * tsrc[8] + tmp[9] * tsrc[9]; + m[3][3] -= tmp[8] * tsrc[9] + tmp[11] * tsrc[0] + tmp[5] * tsrc[8]; + + /* calculate determinant */ + det = tsrc[0] * m[0][0] + tsrc[1] * m[0][1] + tsrc[2] * m[0][2] + + tsrc[3] * m[0][3]; + + /* calculate matrix inverse */ + det = 1.0f / det; + + for (j = 0; j < 4; j++) { + for (i = 0; i < 4; i++) { + m[j][i] *= det; + } + } +} + +void Matrix::Mult(float dst[4][4], float m0[4][4], float m1[4][4]) { + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + dst[i][j] = 0; + for (int k = 0; k < 4; ++k) { + dst[i][j] += m0[k][j] * m1[i][k]; + } + } + } +} + +void Matrix::MultV(float dst[3], float m[4][4], float v[3]) { + // printf("v = %f, %f, %f\n", v[0], v[1], v[2]); + dst[0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2] + m[3][0]; + dst[1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2] + m[3][1]; + dst[2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2] + m[3][2]; + // printf("m = %f, %f, %f\n", m[3][0], m[3][1], m[3][2]); + // printf("dst = %f, %f, %f\n", dst[0], dst[1], dst[2]); +} diff --git a/examples/raytrace/mesh.h b/examples/raytrace/mesh.h new file mode 100644 index 0000000..4261673 --- /dev/null +++ b/examples/raytrace/mesh.h @@ -0,0 +1,188 @@ +#ifndef EXAMPLE_MESH_H_ +#define EXAMPLE_MESH_H_ + +#include +#include +#include +#include + +namespace example { + + +template +inline void lerp(T dst[3], const T v0[3], const T v1[3], const T v2[3], float u, float v) { + dst[0] = (static_cast(1.0) - u - v) * v0[0] + u * v1[0] + v * v2[0]; + dst[1] = (static_cast(1.0) - u - v) * v0[1] + u * v1[1] + v * v2[1]; + dst[2] = (static_cast(1.0) - u - v) * v0[2] + u * v1[2] + v * v2[2]; +} + +template +inline T vlength(const T v[3]) { + const T d = v[0] * v[0] + v[1] * v[1] + v[2] * v[2]; + if (std::fabs(d) > std::numeric_limits::epsilon()) { + return std::sqrt(d); + } else { + return static_cast(0.0); + } +} + +template +inline void vnormalize(T dst[3], const T v[3]) { + dst[0] = v[0]; + dst[1] = v[1]; + dst[2] = v[2]; + const T len = vlength(v); + if (std::fabs(len) > std::numeric_limits::epsilon()) { + const T inv_len = static_cast(1.0) / len; + dst[0] *= inv_len; + dst[1] *= inv_len; + dst[2] *= inv_len; + } +} + +template +inline void vcross(T dst[3], const T a[3], const T b[3]) { + dst[0] = a[1] * b[2] - a[2] * b[1]; + dst[1] = a[2] * b[0] - a[0] * b[2]; + dst[2] = a[0] * b[1] - a[1] * b[0]; +} + +template +inline void vsub(T dst[3], const T a[3], const T b[3]) { + dst[0] = a[0] - b[0]; + dst[1] = a[1] - b[1]; + dst[2] = a[2] - b[2]; +} + +template +inline void calculate_normal(T Nn[3], const T v0[3], const T v1[3], const T v2[3]) { + T v10[3]; + T v20[3]; + + vsub(v10, v1, v0); + vsub(v20, v2, v0); + + T N[3]; + vcross(N, v20, v10); + vnormalize(Nn, N); +} + +template +class Mesh { + public: + explicit Mesh(const size_t vertex_stride) : + stride(vertex_stride) { + } + + std::string name; + + std::vector vertices; /// stride * num_vertices + std::vector facevarying_normals; /// [xyz] * 3(triangle) * num_faces + std::vector facevarying_tangents; /// [xyz] * 3(triangle) * num_faces + std::vector facevarying_binormals; /// [xyz] * 3(triangle) * num_faces + std::vector facevarying_uvs; /// [xy] * 3(triangle) * num_faces + std::vector + facevarying_vertex_colors; /// [xyz] * 3(triangle) * num_faces + std::vector faces; /// triangle x num_faces + std::vector material_ids; /// index x num_faces + + T pivot_xform[4][4]; + size_t stride; /// stride for vertex data. + + // --- Required methods in Scene::Traversal. --- + + /// + /// Get the geometric normal and the shading normal at `face_idx' th face. + /// + void GetNormal(T Ng[3], T Ns[3], const unsigned int face_idx, const T u, const T v) const { + // Compute geometric normal. + unsigned int f0, f1, f2; + T v0[3], v1[3], v2[3]; + + f0 = faces[3 * face_idx + 0]; + f1 = faces[3 * face_idx + 1]; + f2 = faces[3 * face_idx + 2]; + + v0[0] = vertices[3 * f0 + 0]; + v0[1] = vertices[3 * f0 + 1]; + v0[2] = vertices[3 * f0 + 2]; + + v1[0] = vertices[3 * f1 + 0]; + v1[1] = vertices[3 * f1 + 1]; + v1[2] = vertices[3 * f1 + 2]; + + v2[0] = vertices[3 * f2 + 0]; + v2[1] = vertices[3 * f2 + 1]; + v2[2] = vertices[3 * f2 + 2]; + + calculate_normal(Ng, v0, v1, v2); + + if (facevarying_normals.size() > 0) { + + T n0[3], n1[3], n2[3]; + + n0[0] = facevarying_normals[9 * face_idx + 0]; + n0[1] = facevarying_normals[9 * face_idx + 1]; + n0[2] = facevarying_normals[9 * face_idx + 2]; + + n1[0] = facevarying_normals[9 * face_idx + 3]; + n1[1] = facevarying_normals[9 * face_idx + 4]; + n1[2] = facevarying_normals[9 * face_idx + 5]; + + n2[0] = facevarying_normals[9 * face_idx + 6]; + n2[1] = facevarying_normals[9 * face_idx + 7]; + n2[2] = facevarying_normals[9 * face_idx + 8]; + + lerp(Ns, n0, n1, n2, u, v); + + } else { + + // Use geometric normal. + Ns[0] = Ng[0]; + Ns[1] = Ng[1]; + Ns[2] = Ng[2]; + + } + + } + + // --- end of required methods in Scene::Traversal. --- + + /// + /// Get texture coordinate at `face_idx' th face. + /// + void GetTexCoord(T tcoord[3], const unsigned int face_idx, const T u, const T v) { + + if (facevarying_uvs.size() > 0) { + + T t0[3], t1[3], t2[3]; + + t0[0] = facevarying_uvs[6 * face_idx + 0]; + t0[1] = facevarying_uvs[6 * face_idx + 1]; + t0[2] = static_cast(0.0); + + t1[0] = facevarying_uvs[6 * face_idx + 2]; + t1[1] = facevarying_uvs[6 * face_idx + 3]; + t1[2] = static_cast(0.0); + + t2[0] = facevarying_uvs[6 * face_idx + 4]; + t2[1] = facevarying_uvs[6 * face_idx + 5]; + t2[2] = static_cast(0.0); + + lerp(tcoord, t0, t1, t2, u, v); + + } else { + + tcoord[0] = static_cast(0.0); + tcoord[1] = static_cast(0.0); + tcoord[2] = static_cast(0.0); + + } + + } + +}; + +} // namespace example + +#endif // EXAMPLE_MESH_H_ diff --git a/examples/raytrace/nanosg.h b/examples/raytrace/nanosg.h new file mode 100644 index 0000000..8e1ef28 --- /dev/null +++ b/examples/raytrace/nanosg.h @@ -0,0 +1,876 @@ +/* +The MIT License (MIT) + +Copyright (c) 2017 Light Transport Entertainment, Inc. + +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. +*/ + +#ifndef NANOSG_H_ +#define NANOSG_H_ + +#ifdef _WIN32 +#ifndef NOMINMAX +#define NOMINMAX +#endif +#endif + +#include +#include +#include + +#include "nanort.h" + +namespace nanosg { + +template +class PrimitiveInterface; + +template +class PrimitiveInterface { + public: + void print() { static_cast(this)->print(); } +}; + +class SpherePrimitive : PrimitiveInterface { + public: + void print() { std::cout << "Sphere" << std::endl; } +}; + +// 4x4 matrix +template +class Matrix { + public: + Matrix(); + ~Matrix(); + + static void Print(const T m[4][4]) { + for (int i = 0; i < 4; i++) { + printf("m[%d] = %f, %f, %f, %f\n", i, m[i][0], m[i][1], m[i][2], m[i][3]); + } + } + + static void Identity(T m[4][4]) { + m[0][0] = static_cast(1); + m[0][1] = static_cast(0); + m[0][2] = static_cast(0); + m[0][3] = static_cast(0); + m[1][0] = static_cast(0); + m[1][1] = static_cast(1); + m[1][2] = static_cast(0); + m[1][3] = static_cast(0); + m[2][0] = static_cast(0); + m[2][1] = static_cast(0); + m[2][2] = static_cast(1); + m[2][3] = static_cast(0); + m[3][0] = static_cast(0); + m[3][1] = static_cast(0); + m[3][2] = static_cast(0); + m[3][3] = static_cast(1); + } + + static void Copy(T dst[4][4], const T src[4][4]) { + memcpy(dst, src, sizeof(T) * 16); + } + + static void Inverse(T m[4][4]) { + /* + * codes from intel web + * cramer's rule version + */ + int i, j; + T tmp[12]; /* tmp array for pairs */ + T tsrc[16]; /* array of transpose source matrix */ + T det; /* determinant */ + + /* transpose matrix */ + for (i = 0; i < 4; i++) { + tsrc[i] = m[i][0]; + tsrc[i + 4] = m[i][1]; + tsrc[i + 8] = m[i][2]; + tsrc[i + 12] = m[i][3]; + } + + /* calculate pair for first 8 elements(cofactors) */ + tmp[0] = tsrc[10] * tsrc[15]; + tmp[1] = tsrc[11] * tsrc[14]; + tmp[2] = tsrc[9] * tsrc[15]; + tmp[3] = tsrc[11] * tsrc[13]; + tmp[4] = tsrc[9] * tsrc[14]; + tmp[5] = tsrc[10] * tsrc[13]; + tmp[6] = tsrc[8] * tsrc[15]; + tmp[7] = tsrc[11] * tsrc[12]; + tmp[8] = tsrc[8] * tsrc[14]; + tmp[9] = tsrc[10] * tsrc[12]; + tmp[10] = tsrc[8] * tsrc[13]; + tmp[11] = tsrc[9] * tsrc[12]; + + /* calculate first 8 elements(cofactors) */ + m[0][0] = tmp[0] * tsrc[5] + tmp[3] * tsrc[6] + tmp[4] * tsrc[7]; + m[0][0] -= tmp[1] * tsrc[5] + tmp[2] * tsrc[6] + tmp[5] * tsrc[7]; + m[0][1] = tmp[1] * tsrc[4] + tmp[6] * tsrc[6] + tmp[9] * tsrc[7]; + m[0][1] -= tmp[0] * tsrc[4] + tmp[7] * tsrc[6] + tmp[8] * tsrc[7]; + m[0][2] = tmp[2] * tsrc[4] + tmp[7] * tsrc[5] + tmp[10] * tsrc[7]; + m[0][2] -= tmp[3] * tsrc[4] + tmp[6] * tsrc[5] + tmp[11] * tsrc[7]; + m[0][3] = tmp[5] * tsrc[4] + tmp[8] * tsrc[5] + tmp[11] * tsrc[6]; + m[0][3] -= tmp[4] * tsrc[4] + tmp[9] * tsrc[5] + tmp[10] * tsrc[6]; + m[1][0] = tmp[1] * tsrc[1] + tmp[2] * tsrc[2] + tmp[5] * tsrc[3]; + m[1][0] -= tmp[0] * tsrc[1] + tmp[3] * tsrc[2] + tmp[4] * tsrc[3]; + m[1][1] = tmp[0] * tsrc[0] + tmp[7] * tsrc[2] + tmp[8] * tsrc[3]; + m[1][1] -= tmp[1] * tsrc[0] + tmp[6] * tsrc[2] + tmp[9] * tsrc[3]; + m[1][2] = tmp[3] * tsrc[0] + tmp[6] * tsrc[1] + tmp[11] * tsrc[3]; + m[1][2] -= tmp[2] * tsrc[0] + tmp[7] * tsrc[1] + tmp[10] * tsrc[3]; + m[1][3] = tmp[4] * tsrc[0] + tmp[9] * tsrc[1] + tmp[10] * tsrc[2]; + m[1][3] -= tmp[5] * tsrc[0] + tmp[8] * tsrc[1] + tmp[11] * tsrc[2]; + + /* calculate pairs for second 8 elements(cofactors) */ + tmp[0] = tsrc[2] * tsrc[7]; + tmp[1] = tsrc[3] * tsrc[6]; + tmp[2] = tsrc[1] * tsrc[7]; + tmp[3] = tsrc[3] * tsrc[5]; + tmp[4] = tsrc[1] * tsrc[6]; + tmp[5] = tsrc[2] * tsrc[5]; + tmp[6] = tsrc[0] * tsrc[7]; + tmp[7] = tsrc[3] * tsrc[4]; + tmp[8] = tsrc[0] * tsrc[6]; + tmp[9] = tsrc[2] * tsrc[4]; + tmp[10] = tsrc[0] * tsrc[5]; + tmp[11] = tsrc[1] * tsrc[4]; + + /* calculate second 8 elements(cofactors) */ + m[2][0] = tmp[0] * tsrc[13] + tmp[3] * tsrc[14] + tmp[4] * tsrc[15]; + m[2][0] -= tmp[1] * tsrc[13] + tmp[2] * tsrc[14] + tmp[5] * tsrc[15]; + m[2][1] = tmp[1] * tsrc[12] + tmp[6] * tsrc[14] + tmp[9] * tsrc[15]; + m[2][1] -= tmp[0] * tsrc[12] + tmp[7] * tsrc[14] + tmp[8] * tsrc[15]; + m[2][2] = tmp[2] * tsrc[12] + tmp[7] * tsrc[13] + tmp[10] * tsrc[15]; + m[2][2] -= tmp[3] * tsrc[12] + tmp[6] * tsrc[13] + tmp[11] * tsrc[15]; + m[2][3] = tmp[5] * tsrc[12] + tmp[8] * tsrc[13] + tmp[11] * tsrc[14]; + m[2][3] -= tmp[4] * tsrc[12] + tmp[9] * tsrc[13] + tmp[10] * tsrc[14]; + m[3][0] = tmp[2] * tsrc[10] + tmp[5] * tsrc[11] + tmp[1] * tsrc[9]; + m[3][0] -= tmp[4] * tsrc[11] + tmp[0] * tsrc[9] + tmp[3] * tsrc[10]; + m[3][1] = tmp[8] * tsrc[11] + tmp[0] * tsrc[8] + tmp[7] * tsrc[10]; + m[3][1] -= tmp[6] * tsrc[10] + tmp[9] * tsrc[11] + tmp[1] * tsrc[8]; + m[3][2] = tmp[6] * tsrc[9] + tmp[11] * tsrc[11] + tmp[3] * tsrc[8]; + m[3][2] -= tmp[10] * tsrc[11] + tmp[2] * tsrc[8] + tmp[7] * tsrc[9]; + m[3][3] = tmp[10] * tsrc[10] + tmp[4] * tsrc[8] + tmp[9] * tsrc[9]; + m[3][3] -= tmp[8] * tsrc[9] + tmp[11] * tsrc[0] + tmp[5] * tsrc[8]; + + /* calculate determinant */ + det = tsrc[0] * m[0][0] + tsrc[1] * m[0][1] + tsrc[2] * m[0][2] + + tsrc[3] * m[0][3]; + + /* calculate matrix inverse */ + det = static_cast(1.0) / det; + + for (j = 0; j < 4; j++) { + for (i = 0; i < 4; i++) { + m[j][i] *= det; + } + } + } + + static void Transpose(T m[4][4]) { + T t[4][4]; + + // Transpose + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + t[j][i] = m[i][j]; + } + } + + // Copy + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + m[j][i] = t[j][i]; + } + } + } + + static void Mult(T dst[4][4], const T m0[4][4], const T m1[4][4]) { + for (int i = 0; i < 4; ++i) { + for (int j = 0; j < 4; ++j) { + dst[i][j] = 0; + for (int k = 0; k < 4; ++k) { + dst[i][j] += m0[k][j] * m1[i][k]; + } + } + } + } + + static void MultV(T dst[3], const T m[4][4], const T v[3]) { + T tmp[3]; + tmp[0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2] + m[3][0]; + tmp[1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2] + m[3][1]; + tmp[2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2] + m[3][2]; + dst[0] = tmp[0]; + dst[1] = tmp[1]; + dst[2] = tmp[2]; + } + + static void MultV(nanort::real3 &dst, const T m[4][4], const T v[3]) { + T tmp[3]; + tmp[0] = m[0][0] * v[0] + m[1][0] * v[1] + m[2][0] * v[2] + m[3][0]; + tmp[1] = m[0][1] * v[0] + m[1][1] * v[1] + m[2][1] * v[2] + m[3][1]; + tmp[2] = m[0][2] * v[0] + m[1][2] * v[1] + m[2][2] * v[2] + m[3][2]; + dst[0] = tmp[0]; + dst[1] = tmp[1]; + dst[2] = tmp[2]; + } +}; + +// typedef Matrix Matrixf; +// typedef Matrix Matrixd; + +template +static void XformBoundingBox(T xbmin[3], // out + T xbmax[3], // out + T bmin[3], T bmax[3], T m[4][4]) { + // create bounding vertex from (bmin, bmax) + T b[8][3]; + + b[0][0] = bmin[0]; + b[0][1] = bmin[1]; + b[0][2] = bmin[2]; + b[1][0] = bmax[0]; + b[1][1] = bmin[1]; + b[1][2] = bmin[2]; + b[2][0] = bmin[0]; + b[2][1] = bmax[1]; + b[2][2] = bmin[2]; + b[3][0] = bmax[0]; + b[3][1] = bmax[1]; + b[3][2] = bmin[2]; + + b[4][0] = bmin[0]; + b[4][1] = bmin[1]; + b[4][2] = bmax[2]; + b[5][0] = bmax[0]; + b[5][1] = bmin[1]; + b[5][2] = bmax[2]; + b[6][0] = bmin[0]; + b[6][1] = bmax[1]; + b[6][2] = bmax[2]; + b[7][0] = bmax[0]; + b[7][1] = bmax[1]; + b[7][2] = bmax[2]; + + T xb[8][3]; + for (int i = 0; i < 8; i++) { + Matrix::MultV(xb[i], m, b[i]); + } + + xbmin[0] = xb[0][0]; + xbmin[1] = xb[0][1]; + xbmin[2] = xb[0][2]; + xbmax[0] = xb[0][0]; + xbmax[1] = xb[0][1]; + xbmax[2] = xb[0][2]; + + for (int i = 1; i < 8; i++) { + xbmin[0] = std::min(xb[i][0], xbmin[0]); + xbmin[1] = std::min(xb[i][1], xbmin[1]); + xbmin[2] = std::min(xb[i][2], xbmin[2]); + + xbmax[0] = std::max(xb[i][0], xbmax[0]); + xbmax[1] = std::max(xb[i][1], xbmax[1]); + xbmax[2] = std::max(xb[i][2], xbmax[2]); + } +} + +template +struct Intersection { + // required fields. + T t; // hit distance + unsigned int prim_id; // primitive ID of the hit + float u; + float v; + + unsigned int node_id; // node ID of the hit. + nanort::real3 P; // intersection point + nanort::real3 Ns; // shading normal + nanort::real3 Ng; // geometric normal +}; + +/// +/// Renderable node +/// +template +class Node { + public: + typedef Node type; + + explicit Node(const M *mesh) : mesh_(mesh) { + xbmin_[0] = xbmin_[1] = xbmin_[2] = std::numeric_limits::max(); + xbmax_[0] = xbmax_[1] = xbmax_[2] = -std::numeric_limits::max(); + + lbmin_[0] = lbmin_[1] = lbmin_[2] = std::numeric_limits::max(); + lbmax_[0] = lbmax_[1] = lbmax_[2] = -std::numeric_limits::max(); + + Matrix::Identity(local_xform_); + Matrix::Identity(xform_); + Matrix::Identity(inv_xform_); + Matrix::Identity(inv_xform33_); + inv_xform33_[3][3] = static_cast(0.0); + Matrix::Identity(inv_transpose_xform33_); + inv_transpose_xform33_[3][3] = static_cast(0.0); + } + + ~Node() {} + + void Copy(const type &rhs) { + Matrix::Copy(local_xform_, rhs.local_xform_); + Matrix::Copy(xform_, rhs.xform_); + Matrix::Copy(inv_xform_, rhs.inv_xform_); + Matrix::Copy(inv_xform33_, rhs.inv_xform33_); + Matrix::Copy(inv_transpose_xform33_, rhs.inv_transpose_xform33_); + + lbmin_[0] = rhs.lbmin_[0]; + lbmin_[1] = rhs.lbmin_[1]; + lbmin_[2] = rhs.lbmin_[2]; + + lbmax_[0] = rhs.lbmax_[0]; + lbmax_[1] = rhs.lbmax_[1]; + lbmax_[2] = rhs.lbmax_[2]; + + xbmin_[0] = rhs.xbmin_[0]; + xbmin_[1] = rhs.xbmin_[1]; + xbmin_[2] = rhs.xbmin_[2]; + + xbmax_[0] = rhs.xbmax_[0]; + xbmax_[1] = rhs.xbmax_[1]; + xbmax_[2] = rhs.xbmax_[2]; + + mesh_ = rhs.mesh_; + name_ = rhs.name_; + + children_ = rhs.children_; + } + + Node(const type &rhs) { Copy(rhs); } + + const type &operator=(const type &rhs) { + Copy(rhs); + return (*this); + } + + void SetName(const std::string &name) { name_ = name; } + + const std::string &GetName() const { return name_; } + + /// + /// Add child node. + /// + void AddChild(const type &child) { children_.push_back(child); } + + /// + /// Get chidren + /// + const std::vector &GetChildren() const { return children_; } + + std::vector &GetChildren() { return children_; } + + /// + /// Update internal state. + /// + void Update(const T parent_xform[4][4]) { + if (!accel_.IsValid() && mesh_ && (mesh_->vertices.size() > 3) && + (mesh_->faces.size() >= 3)) { + // Assume mesh is composed of triangle faces only. + nanort::TriangleMesh triangle_mesh( + mesh_->vertices.data(), mesh_->faces.data(), mesh_->stride); + nanort::TriangleSAHPred triangle_pred( + mesh_->vertices.data(), mesh_->faces.data(), mesh_->stride); + + bool ret = + accel_.Build(static_cast(mesh_->faces.size()) / 3, + triangle_mesh, triangle_pred); + + // Update local bbox. + if (ret) { + accel_.BoundingBox(lbmin_, lbmax_); + } + } + + // xform = parent_xform x local_xform + Matrix::Mult(xform_, parent_xform, local_xform_); + + // Compute the bounding box in world coordinate. + XformBoundingBox(xbmin_, xbmax_, lbmin_, lbmax_, xform_); + + // Inverse(xform) + Matrix::Copy(inv_xform_, xform_); + Matrix::Inverse(inv_xform_); + + // Clear translation, then inverse(xform) + Matrix::Copy(inv_xform33_, xform_); + inv_xform33_[3][0] = static_cast(0.0); + inv_xform33_[3][1] = static_cast(0.0); + inv_xform33_[3][2] = static_cast(0.0); + Matrix::Inverse(inv_xform33_); + + // Inverse transpose of xform33 + Matrix::Copy(inv_transpose_xform33_, inv_xform33_); + Matrix::Transpose(inv_transpose_xform33_); + + // Update children nodes + for (size_t i = 0; i < children_.size(); i++) { + children_[i].Update(xform_); + } + } + + /// + /// Set local transformation. + /// + void SetLocalXform(const T xform[4][4]) { + memcpy(local_xform_, xform, sizeof(float) * 16); + } + + const T *GetLocalXformPtr() const { return &local_xform_[0][0]; } + + const T *GetXformPtr() const { return &xform_[0][0]; } + + const M *GetMesh() const { return mesh_; } + + const nanort::BVHAccel &GetAccel() const { return accel_; } + + inline void GetWorldBoundingBox(T bmin[3], T bmax[3]) const { + bmin[0] = xbmin_[0]; + bmin[1] = xbmin_[1]; + bmin[2] = xbmin_[2]; + + bmax[0] = xbmax_[0]; + bmax[1] = xbmax_[1]; + bmax[2] = xbmax_[2]; + } + + inline void GetLocalBoundingBox(T bmin[3], T bmax[3]) const { + bmin[0] = lbmin_[0]; + bmin[1] = lbmin_[1]; + bmin[2] = lbmin_[2]; + + bmax[0] = lbmax_[0]; + bmax[1] = lbmax_[1]; + bmax[2] = lbmax_[2]; + } + + T local_xform_[4][4]; // Node's local transformation matrix. + T xform_[4][4]; // Parent xform x local_xform. + T inv_xform_[4][4]; // inverse(xform). world -> local + T inv_xform33_[4][4]; // inverse(xform0 with upper-left 3x3 elemets only(for + // transforming direction vector) + T inv_transpose_xform33_[4][4]; // inverse(transpose(xform)) with upper-left + // 3x3 elements only(for transforming normal + // vector) + + private: + // bounding box(local space) + T lbmin_[3]; + T lbmax_[3]; + + // bounding box after xform(world space) + T xbmin_[3]; + T xbmax_[3]; + + nanort::BVHAccel accel_; + + std::string name_; + + const M *mesh_; + + std::vector children_; +}; + +// ------------------------------------------------- + +// Predefined SAH predicator for cube. +template +class NodeBBoxPred { + public: + NodeBBoxPred(const std::vector > *nodes) + : axis_(0), pos_(0.0f), nodes_(nodes) {} + + void Set(int axis, float pos) const { + axis_ = axis; + pos_ = pos; + } + + bool operator()(unsigned int i) const { + int axis = axis_; + float pos = pos_; + + T bmin[3], bmax[3]; + + (*nodes_)[i].GetWorldBoundingBox(bmin, bmax); + + T center = bmax[axis] - bmin[axis]; + + return (center < pos); + } + + private: + mutable int axis_; + mutable float pos_; + const std::vector > *nodes_; +}; + +template +class NodeBBoxGeometry { + public: + NodeBBoxGeometry(const std::vector > *nodes) : nodes_(nodes) {} + + /// Compute bounding box for `prim_index`th cube. + /// This function is called for each primitive in BVH build. + void BoundingBox(nanort::real3 *bmin, nanort::real3 *bmax, + unsigned int prim_index) const { + T a[3], b[3]; + (*nodes_)[prim_index].GetWorldBoundingBox(a, b); + (*bmin)[0] = a[0]; + (*bmin)[1] = a[1]; + (*bmin)[2] = a[2]; + (*bmax)[0] = b[0]; + (*bmax)[1] = b[1]; + (*bmax)[2] = b[2]; + } + + const std::vector > *nodes_; + mutable nanort::real3 ray_org_; + mutable nanort::real3 ray_dir_; + mutable nanort::BVHTraceOptions trace_options_; + int _pad_; +}; + +class NodeBBoxIntersection { + public: + NodeBBoxIntersection() {} + + float normal[3]; + + // Required member variables. + float t; + unsigned int prim_id; +}; + +template +class NodeBBoxIntersector { + public: + NodeBBoxIntersector(const std::vector > *nodes) : nodes_(nodes) {} + + bool Intersect(float *out_t_min, float *out_t_max, + unsigned int prim_index) const { + T bmin[3], bmax[3]; + + (*nodes_)[prim_index].GetWorldBoundingBox(bmin, bmax); + + float tmin, tmax; + + const float min_x = ray_dir_sign_[0] ? bmax[0] : bmin[0]; + const float min_y = ray_dir_sign_[1] ? bmax[1] : bmin[1]; + const float min_z = ray_dir_sign_[2] ? bmax[2] : bmin[2]; + const float max_x = ray_dir_sign_[0] ? bmin[0] : bmax[0]; + const float max_y = ray_dir_sign_[1] ? bmin[1] : bmax[1]; + const float max_z = ray_dir_sign_[2] ? bmin[2] : bmax[2]; + + // X + const float tmin_x = (min_x - ray_org_[0]) * ray_inv_dir_[0]; + const float tmax_x = (max_x - ray_org_[0]) * ray_inv_dir_[0]; + + // Y + const float tmin_y = (min_y - ray_org_[1]) * ray_inv_dir_[1]; + const float tmax_y = (max_y - ray_org_[1]) * ray_inv_dir_[1]; + + // Z + const float tmin_z = (min_z - ray_org_[2]) * ray_inv_dir_[2]; + const float tmax_z = (max_z - ray_org_[2]) * ray_inv_dir_[2]; + + tmin = nanort::safemax(tmin_z, nanort::safemax(tmin_y, tmin_x)); + tmax = nanort::safemin(tmax_z, nanort::safemin(tmax_y, tmax_x)); + + if (tmin <= tmax) { + (*out_t_min) = tmin; + (*out_t_max) = tmax; + return true; + } + + return false; + } + + /// Prepare BVH traversal(e.g. compute inverse ray direction) + /// This function is called only once in BVH traversal. + void PrepareTraversal(const nanort::Ray &ray) const { + ray_org_[0] = ray.org[0]; + ray_org_[1] = ray.org[1]; + ray_org_[2] = ray.org[2]; + + ray_dir_[0] = ray.dir[0]; + ray_dir_[1] = ray.dir[1]; + ray_dir_[2] = ray.dir[2]; + + // FIXME(syoyo): Consider zero div case. + ray_inv_dir_[0] = static_cast(1.0) / ray.dir[0]; + ray_inv_dir_[1] = static_cast(1.0) / ray.dir[1]; + ray_inv_dir_[2] = static_cast(1.0) / ray.dir[2]; + + ray_dir_sign_[0] = ray.dir[0] < static_cast(0.0) ? 1 : 0; + ray_dir_sign_[1] = ray.dir[1] < static_cast(0.0) ? 1 : 0; + ray_dir_sign_[2] = ray.dir[2] < static_cast(0.0) ? 1 : 0; + } + + const std::vector > *nodes_; + mutable nanort::real3 ray_org_; + mutable nanort::real3 ray_dir_; + mutable nanort::real3 ray_inv_dir_; + mutable int ray_dir_sign_[3]; +}; + +template +class Scene { + public: + Scene() { + bmin_[0] = bmin_[1] = bmin_[2] = std::numeric_limits::max(); + bmax_[0] = bmax_[1] = bmax_[2] = -std::numeric_limits::max(); + } + + ~Scene() {} + + /// + /// Add intersectable node to the scene. + /// + bool AddNode(const Node &node) { + nodes_.push_back(node); + return true; + } + + const std::vector > &GetNodes() const { return nodes_; } + + bool FindNode(const std::string &name, Node **found_node) { + if (!found_node) { + return false; + } + + if (name.empty()) { + return false; + } + + // Simple exhaustive search. + for (size_t i = 0; i < nodes_.size(); i++) { + if (FindNodeRecursive(name, &(nodes_[i]), found_node)) { + return true; + } + } + + return false; + } + + /// + /// Commit the scene. Must be called before tracing rays into the scene. + /// + bool Commit() { + // Update nodes. + for (size_t i = 0; i < nodes_.size(); i++) { + T ident[4][4]; + Matrix::Identity(ident); + + nodes_[i].Update(ident); + } + + // Build toplevel BVH. + NodeBBoxGeometry geom(&nodes_); + NodeBBoxPred pred(&nodes_); + + // FIXME(LTE): Limit one leaf contains one node bbox primitive. This would + // work, but would be inefficient. + // e.g. will miss some node when constructed BVH depth is larger than the + // value of BVHBuildOptions. + // Implement more better and efficient BVH build and traverse for Toplevel + // BVH. + nanort::BVHBuildOptions build_options; + build_options.min_leaf_primitives = 1; + + bool ret = toplevel_accel_.Build(static_cast(nodes_.size()), + geom, pred, build_options); + + nanort::BVHBuildStatistics stats = toplevel_accel_.GetStatistics(); + (void)stats; + + // toplevel_accel_.Debug(); + + if (ret) { + toplevel_accel_.BoundingBox(bmin_, bmax_); + } else { + // Set invalid bbox value. + bmin_[0] = std::numeric_limits::max(); + bmin_[1] = std::numeric_limits::max(); + bmin_[2] = std::numeric_limits::max(); + + bmax_[0] = -std::numeric_limits::max(); + bmax_[1] = -std::numeric_limits::max(); + bmax_[2] = -std::numeric_limits::max(); + } + + return ret; + } + + /// + /// Get the scene bounding box. + /// + void GetBoundingBox(T bmin[3], T bmax[3]) const { + bmin[0] = bmin_[0]; + bmin[1] = bmin_[1]; + bmin[2] = bmin_[2]; + + bmax[0] = bmax_[0]; + bmax[1] = bmax_[1]; + bmax[2] = bmax_[2]; + } + + /// + /// Trace the ray into the scene. + /// First find the intersection of nodes' bounding box using toplevel BVH. + /// Then, trace into the hit node to find the intersection of the primitive. + /// + template + bool Traverse(nanort::Ray &ray, H *isect, + const bool cull_back_face = false) const { + if (!toplevel_accel_.IsValid()) { + return false; + } + + const int kMaxIntersections = 64; + + bool has_hit = false; + + NodeBBoxIntersector isector(&nodes_); + nanort::StackVector, 128> node_hits; + bool may_hit = toplevel_accel_.ListNodeIntersections(ray, kMaxIntersections, + isector, &node_hits); + + if (may_hit) { + T t_max = std::numeric_limits::max(); + T t_nearest = t_max; + + nanort::BVHTraceOptions trace_options; + trace_options.cull_back_face = cull_back_face; + + // Find actual intersection point. + for (size_t i = 0; i < node_hits->size(); i++) { + // Early cull test. + if (t_nearest < node_hits[i].t_min) { + // printf("near: %f, t_min: %f, t_max: %f\n", t_nearest, + // node_hits[i].t_min, node_hits[i].t_max); + continue; + } + + assert(node_hits[i].node_id < nodes_.size()); + const Node &node = nodes_[node_hits[i].node_id]; + + // Transform ray into node's local space + // TODO(LTE): Set ray tmin and tmax + nanort::Ray local_ray; + Matrix::MultV(local_ray.org, node.inv_xform_, ray.org); + Matrix::MultV(local_ray.dir, node.inv_xform33_, ray.dir); + + nanort::TriangleIntersector triangle_intersector( + node.GetMesh()->vertices.data(), node.GetMesh()->faces.data(), + node.GetMesh()->stride); + H local_isect; + + bool hit = node.GetAccel().Traverse(local_ray, triangle_intersector, + &local_isect); + + if (hit) { + // Calulcate hit distance in world coordiante. + T local_P[3]; + local_P[0] = local_ray.org[0] + local_isect.t * local_ray.dir[0]; + local_P[1] = local_ray.org[1] + local_isect.t * local_ray.dir[1]; + local_P[2] = local_ray.org[2] + local_isect.t * local_ray.dir[2]; + + T world_P[3]; + Matrix::MultV(world_P, node.xform_, local_P); + + nanort::real3 po; + po[0] = world_P[0] - ray.org[0]; + po[1] = world_P[1] - ray.org[1]; + po[2] = world_P[2] - ray.org[2]; + + float t_world = vlength(po); + // printf("tworld %f, tnear %f\n", t_world, t_nearest); + + if (t_world < t_nearest) { + t_nearest = t_world; + has_hit = true; + //(*isect) = local_isect; + isect->node_id = node_hits[i].node_id; + isect->prim_id = local_isect.prim_id; + isect->u = local_isect.u; + isect->v = local_isect.v; + + // TODO(LTE): Implement + T Ng[3], Ns[3]; // geometric normal, shading normal. + + node.GetMesh()->GetNormal(Ng, Ns, isect->prim_id, isect->u, + isect->v); + + // Convert position and normal into world coordinate. + isect->t = t_world; + Matrix::MultV(isect->P, node.xform_, local_P); + Matrix::MultV(isect->Ng, node.inv_transpose_xform33_, Ng); + Matrix::MultV(isect->Ns, node.inv_transpose_xform33_, Ns); + } + } + } + } + + return has_hit; + } + + private: + /// + /// Find a node by name. + /// + bool FindNodeRecursive(const std::string &name, Node *root, + Node **found_node) { + if (root->GetName().compare(name) == 0) { + (*found_node) = root; + return true; + } + + // Simple exhaustive search. + for (size_t i = 0; i < root->GetChildren().size(); i++) { + if (FindNodeRecursive(name, &(root->GetChildren()[i]), found_node)) { + return true; + } + } + + return false; + } + + // Scene bounding box. + // Valid after calling `Commit()`. + T bmin_[3]; + T bmax_[3]; + + // Toplevel BVH accel. + nanort::BVHAccel toplevel_accel_; + std::vector > nodes_; +}; + +} // namespace nanosg + +#endif // NANOSG_H_ diff --git a/examples/raytrace/obj-loader.cc b/examples/raytrace/obj-loader.cc new file mode 100644 index 0000000..7ebc9b9 --- /dev/null +++ b/examples/raytrace/obj-loader.cc @@ -0,0 +1,459 @@ +#include "obj-loader.h" +#include "../../nanort.h" // for float3 + +#define TINYOBJLOADER_IMPLEMENTATION +#include "tiny_obj_loader.h" + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wreserved-id-macro" +#pragma clang diagnostic ignored "-Wc++98-compat-pedantic" +#pragma clang diagnostic ignored "-Wcast-align" +#pragma clang diagnostic ignored "-Wpadded" +#pragma clang diagnostic ignored "-Wold-style-cast" +#pragma clang diagnostic ignored "-Wsign-conversion" +#pragma clang diagnostic ignored "-Wvariadic-macros" +#pragma clang diagnostic ignored "-Wc++11-extensions" +#pragma clang diagnostic ignored "-Wdisabled-macro-expansion" +#pragma clang diagnostic ignored "-Wimplicit-fallthrough" +#if __has_warning("-Wdouble-promotion") +#pragma clang diagnostic ignored "-Wdouble-promotion" +#endif +#if __has_warning("-Wcomma") +#pragma clang diagnostic ignored "-Wcomma" +#endif +#if __has_warning("-Wcast-qual") +#pragma clang diagnostic ignored "-Wcast-qual" +#endif +#endif + +#define STB_IMAGE_IMPLEMENTATION +#include "stb_image.h" + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +#include + +#ifdef NANOSG_USE_CXX11 +#include +#else +#include +#endif + +#define USE_TEX_CACHE 1 + +namespace example { + +typedef nanort::real3 float3; + +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#pragma clang diagnostic ignored "-Wglobal-constructors" +#endif + +// TODO(LTE): Remove global static definition. +#ifdef NANOSG_USE_CXX11 +static std::unordered_map hashed_tex; +#else +static std::map hashed_tex; +#endif + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +inline void CalcNormal(float3 &N, float3 v0, float3 v1, float3 v2) { + float3 v10 = v1 - v0; + float3 v20 = v2 - v0; + + N = vcross(v20, v10); + N = vnormalize(N); +} + +static std::string GetBaseDir(const std::string &filepath) { + if (filepath.find_last_of("/\\") != std::string::npos) + return filepath.substr(0, filepath.find_last_of("/\\")); + return ""; +} + +static int LoadTexture(const std::string &filename, + std::vector *textures) { + int idx; + + if (filename.empty()) return -1; + + std::cout << " Loading texture : " << filename << std::endl; + Texture texture; + + // tigra: find in cache. get index + if (USE_TEX_CACHE) { + if (hashed_tex.find(filename) != hashed_tex.end()) { + puts("from cache"); + return hashed_tex[filename]; + } + } + + int w, h, n; + unsigned char *data = stbi_load(filename.c_str(), &w, &h, &n, 0); + if (data) { + texture.width = w; + texture.height = h; + texture.components = n; + + size_t n_elem = size_t(w * h * n); + texture.image = new unsigned char[n_elem]; + for (size_t i = 0; i < n_elem; i++) { + texture.image[i] = data[i]; + } + + free(data); + + textures->push_back(texture); + + idx = int(textures->size()) - 1; + + // tigra: store index to cache + if (USE_TEX_CACHE) { + hashed_tex[filename] = idx; + } + + return idx; + } + + std::cout << " Failed to load : " << filename << std::endl; + return -1; +} + +static void ComputeBoundingBoxOfMesh(float bmin[3], float bmax[3], + const example::Mesh &mesh) { + bmin[0] = bmin[1] = bmin[2] = std::numeric_limits::max(); + bmax[0] = bmax[1] = bmax[2] = -std::numeric_limits::max(); + + for (size_t i = 0; i < mesh.vertices.size() / 3; i++) { + bmin[0] = std::min(bmin[0], mesh.vertices[3 * i + 0]); + bmin[1] = std::min(bmin[1], mesh.vertices[3 * i + 1]); + bmin[2] = std::min(bmin[1], mesh.vertices[3 * i + 2]); + + bmax[0] = std::max(bmax[0], mesh.vertices[3 * i + 0]); + bmax[1] = std::max(bmax[1], mesh.vertices[3 * i + 1]); + bmax[2] = std::max(bmax[2], mesh.vertices[3 * i + 2]); + } +} + +bool LoadObj(const std::string &filename, float scale, + std::vector > *meshes, + std::vector *out_materials, + std::vector *out_textures) { + tinyobj::attrib_t attrib; + std::vector shapes; + std::vector materials; + std::string err; + + std::string basedir = GetBaseDir(filename) + "/"; + const char *basepath = (basedir.compare("/") == 0) ? NULL : basedir.c_str(); + + // auto t_start = std::chrono::system_clock::now(); + + bool ret = + tinyobj::LoadObj(&attrib, &shapes, &materials, &err, filename.c_str(), + basepath, /* triangulate */ true); + + // auto t_end = std::chrono::system_clock::now(); + // std::chrono::duration ms = t_end - t_start; + + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!ret) { + return false; + } + + // std::cout << "[LoadOBJ] Parse time : " << ms.count() << " [msecs]" + // << std::endl; + + std::cout << "[LoadOBJ] # of shapes in .obj : " << shapes.size() << std::endl; + std::cout << "[LoadOBJ] # of materials in .obj : " << materials.size() + << std::endl; + + { + size_t total_num_vertices = 0; + size_t total_num_faces = 0; + + total_num_vertices = attrib.vertices.size() / 3; + std::cout << " vertices : " << attrib.vertices.size() / 3 << std::endl; + + for (size_t i = 0; i < shapes.size(); i++) { + std::cout << " shape[" << i << "].name : " << shapes[i].name + << std::endl; + std::cout << " shape[" << i + << "].indices : " << shapes[i].mesh.indices.size() << std::endl; + assert((shapes[i].mesh.indices.size() % 3) == 0); + + total_num_faces += shapes[i].mesh.indices.size() / 3; + + // tigra: empty name convert to _id + if (shapes[i].name.length() == 0) { +#ifdef NANOSG_USE_CXX11 + shapes[i].name = "_" + std::to_string(i); +#else + std::stringstream ss; + ss << i; + shapes[i].name = "_" + ss.str(); +#endif + std::cout << " EMPTY shape[" << i << "].name, new : " << shapes[i].name + << std::endl; + } + } + std::cout << "[LoadOBJ] # of faces: " << total_num_faces << std::endl; + std::cout << "[LoadOBJ] # of vertices: " << total_num_vertices << std::endl; + } + + // TODO(LTE): Implement tangents and binormals + + for (size_t i = 0; i < shapes.size(); i++) { + Mesh mesh(/* stride */ sizeof(float) * 3); + + mesh.name = shapes[i].name; + + const size_t num_faces = shapes[i].mesh.indices.size() / 3; + mesh.faces.resize(num_faces * 3); + mesh.material_ids.resize(num_faces); + mesh.facevarying_normals.resize(num_faces * 3 * 3); + mesh.facevarying_uvs.resize(num_faces * 3 * 2); + mesh.vertices.resize(num_faces * 3 * 3); + + for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) { + // reorder vertices. may create duplicated vertices. + size_t f0 = size_t(shapes[i].mesh.indices[3 * f + 0].vertex_index); + size_t f1 = size_t(shapes[i].mesh.indices[3 * f + 1].vertex_index); + size_t f2 = size_t(shapes[i].mesh.indices[3 * f + 2].vertex_index); + + mesh.vertices[9 * f + 0] = scale * attrib.vertices[3 * f0 + 0]; + mesh.vertices[9 * f + 1] = scale * attrib.vertices[3 * f0 + 1]; + mesh.vertices[9 * f + 2] = scale * attrib.vertices[3 * f0 + 2]; + + mesh.vertices[9 * f + 3] = scale * attrib.vertices[3 * f1 + 0]; + mesh.vertices[9 * f + 4] = scale * attrib.vertices[3 * f1 + 1]; + mesh.vertices[9 * f + 5] = scale * attrib.vertices[3 * f1 + 2]; + + mesh.vertices[9 * f + 6] = scale * attrib.vertices[3 * f2 + 0]; + mesh.vertices[9 * f + 7] = scale * attrib.vertices[3 * f2 + 1]; + mesh.vertices[9 * f + 8] = scale * attrib.vertices[3 * f2 + 2]; + + mesh.faces[3 * f + 0] = static_cast(3 * f + 0); + mesh.faces[3 * f + 1] = static_cast(3 * f + 1); + mesh.faces[3 * f + 2] = static_cast(3 * f + 2); + + mesh.material_ids[f] = + static_cast(shapes[i].mesh.material_ids[f]); + } + + if (attrib.normals.size() > 0) { + for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) { + size_t f0, f1, f2; + + f0 = size_t(shapes[i].mesh.indices[3 * f + 0].normal_index); + f1 = size_t(shapes[i].mesh.indices[3 * f + 1].normal_index); + f2 = size_t(shapes[i].mesh.indices[3 * f + 2].normal_index); + + if (f0 > 0 && f1 > 0 && f2 > 0) { + float n0[3], n1[3], n2[3]; + + n0[0] = attrib.normals[3 * f0 + 0]; + n0[1] = attrib.normals[3 * f0 + 1]; + n0[2] = attrib.normals[3 * f0 + 2]; + + n1[0] = attrib.normals[3 * f1 + 0]; + n1[1] = attrib.normals[3 * f1 + 1]; + n1[2] = attrib.normals[3 * f1 + 2]; + + n2[0] = attrib.normals[3 * f2 + 0]; + n2[1] = attrib.normals[3 * f2 + 1]; + n2[2] = attrib.normals[3 * f2 + 2]; + + mesh.facevarying_normals[3 * (3 * f + 0) + 0] = n0[0]; + mesh.facevarying_normals[3 * (3 * f + 0) + 1] = n0[1]; + mesh.facevarying_normals[3 * (3 * f + 0) + 2] = n0[2]; + + mesh.facevarying_normals[3 * (3 * f + 1) + 0] = n1[0]; + mesh.facevarying_normals[3 * (3 * f + 1) + 1] = n1[1]; + mesh.facevarying_normals[3 * (3 * f + 1) + 2] = n1[2]; + + mesh.facevarying_normals[3 * (3 * f + 2) + 0] = n2[0]; + mesh.facevarying_normals[3 * (3 * f + 2) + 1] = n2[1]; + mesh.facevarying_normals[3 * (3 * f + 2) + 2] = n2[2]; + } else { // face contains invalid normal index. calc geometric normal. + f0 = size_t(shapes[i].mesh.indices[3 * f + 0].vertex_index); + f1 = size_t(shapes[i].mesh.indices[3 * f + 1].vertex_index); + f2 = size_t(shapes[i].mesh.indices[3 * f + 2].vertex_index); + + float3 v0, v1, v2; + + v0[0] = attrib.vertices[3 * f0 + 0]; + v0[1] = attrib.vertices[3 * f0 + 1]; + v0[2] = attrib.vertices[3 * f0 + 2]; + + v1[0] = attrib.vertices[3 * f1 + 0]; + v1[1] = attrib.vertices[3 * f1 + 1]; + v1[2] = attrib.vertices[3 * f1 + 2]; + + v2[0] = attrib.vertices[3 * f2 + 0]; + v2[1] = attrib.vertices[3 * f2 + 1]; + v2[2] = attrib.vertices[3 * f2 + 2]; + + float3 N; + CalcNormal(N, v0, v1, v2); + + mesh.facevarying_normals[3 * (3 * f + 0) + 0] = N[0]; + mesh.facevarying_normals[3 * (3 * f + 0) + 1] = N[1]; + mesh.facevarying_normals[3 * (3 * f + 0) + 2] = N[2]; + + mesh.facevarying_normals[3 * (3 * f + 1) + 0] = N[0]; + mesh.facevarying_normals[3 * (3 * f + 1) + 1] = N[1]; + mesh.facevarying_normals[3 * (3 * f + 1) + 2] = N[2]; + + mesh.facevarying_normals[3 * (3 * f + 2) + 0] = N[0]; + mesh.facevarying_normals[3 * (3 * f + 2) + 1] = N[1]; + mesh.facevarying_normals[3 * (3 * f + 2) + 2] = N[2]; + } + } + } else { + // calc geometric normal + for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) { + size_t f0, f1, f2; + + f0 = size_t(shapes[i].mesh.indices[3 * f + 0].vertex_index); + f1 = size_t(shapes[i].mesh.indices[3 * f + 1].vertex_index); + f2 = size_t(shapes[i].mesh.indices[3 * f + 2].vertex_index); + + float3 v0, v1, v2; + + v0[0] = attrib.vertices[3 * f0 + 0]; + v0[1] = attrib.vertices[3 * f0 + 1]; + v0[2] = attrib.vertices[3 * f0 + 2]; + + v1[0] = attrib.vertices[3 * f1 + 0]; + v1[1] = attrib.vertices[3 * f1 + 1]; + v1[2] = attrib.vertices[3 * f1 + 2]; + + v2[0] = attrib.vertices[3 * f2 + 0]; + v2[1] = attrib.vertices[3 * f2 + 1]; + v2[2] = attrib.vertices[3 * f2 + 2]; + + float3 N; + CalcNormal(N, v0, v1, v2); + + mesh.facevarying_normals[3 * (3 * f + 0) + 0] = N[0]; + mesh.facevarying_normals[3 * (3 * f + 0) + 1] = N[1]; + mesh.facevarying_normals[3 * (3 * f + 0) + 2] = N[2]; + + mesh.facevarying_normals[3 * (3 * f + 1) + 0] = N[0]; + mesh.facevarying_normals[3 * (3 * f + 1) + 1] = N[1]; + mesh.facevarying_normals[3 * (3 * f + 1) + 2] = N[2]; + + mesh.facevarying_normals[3 * (3 * f + 2) + 0] = N[0]; + mesh.facevarying_normals[3 * (3 * f + 2) + 1] = N[1]; + mesh.facevarying_normals[3 * (3 * f + 2) + 2] = N[2]; + } + } + + if (attrib.texcoords.size() > 0) { + for (size_t f = 0; f < shapes[i].mesh.indices.size() / 3; f++) { + size_t f0, f1, f2; + + f0 = size_t(shapes[i].mesh.indices[3 * f + 0].texcoord_index); + f1 = size_t(shapes[i].mesh.indices[3 * f + 1].texcoord_index); + f2 = size_t(shapes[i].mesh.indices[3 * f + 2].texcoord_index); + + if (f0 > 0 && f1 > 0 && f2 > 0) { + float3 n0, n1, n2; + + n0[0] = attrib.texcoords[2 * f0 + 0]; + n0[1] = attrib.texcoords[2 * f0 + 1]; + + n1[0] = attrib.texcoords[2 * f1 + 0]; + n1[1] = attrib.texcoords[2 * f1 + 1]; + + n2[0] = attrib.texcoords[2 * f2 + 0]; + n2[1] = attrib.texcoords[2 * f2 + 1]; + + mesh.facevarying_uvs[2 * (3 * f + 0) + 0] = n0[0]; + mesh.facevarying_uvs[2 * (3 * f + 0) + 1] = n0[1]; + + mesh.facevarying_uvs[2 * (3 * f + 1) + 0] = n1[0]; + mesh.facevarying_uvs[2 * (3 * f + 1) + 1] = n1[1]; + + mesh.facevarying_uvs[2 * (3 * f + 2) + 0] = n2[0]; + mesh.facevarying_uvs[2 * (3 * f + 2) + 1] = n2[1]; + } + } + } + + // Compute pivot translation and add offset to the vertices. + float bmin[3], bmax[3]; + ComputeBoundingBoxOfMesh(bmin, bmax, mesh); + + float bcenter[3]; + bcenter[0] = 0.5f * (bmax[0] - bmin[0]) + bmin[0]; + bcenter[1] = 0.5f * (bmax[1] - bmin[1]) + bmin[1]; + bcenter[2] = 0.5f * (bmax[2] - bmin[2]) + bmin[2]; + + for (size_t v = 0; v < mesh.vertices.size() / 3; v++) { + mesh.vertices[3 * v + 0] -= bcenter[0]; + mesh.vertices[3 * v + 1] -= bcenter[1]; + mesh.vertices[3 * v + 2] -= bcenter[2]; + } + + mesh.pivot_xform[0][0] = 1.0f; + mesh.pivot_xform[0][1] = 0.0f; + mesh.pivot_xform[0][2] = 0.0f; + mesh.pivot_xform[0][3] = 0.0f; + + mesh.pivot_xform[1][0] = 0.0f; + mesh.pivot_xform[1][1] = 1.0f; + mesh.pivot_xform[1][2] = 0.0f; + mesh.pivot_xform[1][3] = 0.0f; + + mesh.pivot_xform[2][0] = 0.0f; + mesh.pivot_xform[2][1] = 0.0f; + mesh.pivot_xform[2][2] = 1.0f; + mesh.pivot_xform[2][3] = 0.0f; + + mesh.pivot_xform[3][0] = bcenter[0]; + mesh.pivot_xform[3][1] = bcenter[1]; + mesh.pivot_xform[3][2] = bcenter[2]; + mesh.pivot_xform[3][3] = 1.0f; + + meshes->push_back(mesh); + } + + // material_t -> Material and Texture + out_materials->resize(materials.size()); + out_textures->resize(0); + for (size_t i = 0; i < materials.size(); i++) { + (*out_materials)[i].diffuse[0] = materials[i].diffuse[0]; + (*out_materials)[i].diffuse[1] = materials[i].diffuse[1]; + (*out_materials)[i].diffuse[2] = materials[i].diffuse[2]; + (*out_materials)[i].specular[0] = materials[i].specular[0]; + (*out_materials)[i].specular[1] = materials[i].specular[1]; + (*out_materials)[i].specular[2] = materials[i].specular[2]; + + (*out_materials)[i].id = int(i); + + // map_Kd + (*out_materials)[i].diffuse_texid = + LoadTexture(materials[i].diffuse_texname, out_textures); + // map_Ks + (*out_materials)[i].specular_texid = + LoadTexture(materials[i].specular_texname, out_textures); + } + + return true; +} + +} // namespace example diff --git a/examples/raytrace/obj-loader.h b/examples/raytrace/obj-loader.h new file mode 100644 index 0000000..9827791 --- /dev/null +++ b/examples/raytrace/obj-loader.h @@ -0,0 +1,19 @@ +#ifndef EXAMPLE_OBJ_LOADER_H_ +#define EXAMPLE_OBJ_LOADER_H_ + +#include +#include + +#include "mesh.h" +#include "material.h" + +namespace example { + +/// +/// Loads wavefront .obj mesh +/// +bool LoadObj(const std::string &filename, float scale, std::vector > *meshes, std::vector *materials, std::vector *textures); + +} + +#endif // EXAMPLE_OBJ_LOADER_H_ diff --git a/examples/raytrace/premake5.lua b/examples/raytrace/premake5.lua new file mode 100644 index 0000000..e16dc89 --- /dev/null +++ b/examples/raytrace/premake5.lua @@ -0,0 +1,114 @@ +newoption { + trigger = "with-gtk3nfd", + description = "Build with native file dialog support(GTK3 required. Linux only)" +} + +newoption { + trigger = "asan", + description = "Enable Address Sanitizer(gcc5+ ang clang only)" +} + +sources = { + "main.cc", + "render.cc", + "render-config.cc", + "obj-loader.cc", + "matrix.cc", + "../common/trackball.cc", + "../common/imgui/imgui.cpp", + "../common/imgui/imgui_draw.cpp", + "../common/imgui/imgui_impl_btgui.cpp", + "../common/imgui/ImGuizmo.cpp", + } + +solution "NanoSGSolution" + configurations { "Release", "Debug" } + + if os.is("Windows") then + platforms { "x64", "x32" } + else + platforms { "native", "x64", "x32" } + end + + + -- RootDir for OpenGLWindow + projectRootDir = os.getcwd() .. "/../common/" + dofile ("../common/findOpenGLGlewGlut.lua") + initOpenGL() + initGlew() + + -- Use c++11 + flags { "c++11" } + + -- A project defines one build target + project "viwewer" + kind "ConsoleApp" + language "C++" + files { sources } + + includedirs { "./", "../../" } + includedirs { "../common" } + includedirs { "../common/imgui" } + includedirs { "../common/glm" } + --includedirs { "../common/nativefiledialog/src/include" } + + if _OPTIONS['asan'] then + buildoptions { "-fsanitize=address" } + linkoptions { "-fsanitize=address" } + end + + if os.is("Windows") then + flags { "FatalCompileWarnings" } + warnings "Extra" -- /W4 + + defines { "NOMINMAX" } + defines { "USE_NATIVEFILEDIALOG" } + buildoptions { "/W4" } -- raise compile error level. + files{ + "../common/OpenGLWindow/Win32OpenGLWindow.cpp", + "../common/OpenGLWindow/Win32OpenGLWindow.h", + "../common/OpenGLWindow/Win32Window.cpp", + "../common/OpenGLWindow/Win32Window.h", + } + includedirs { "./../common/nativefiledialog/src/include" } + files { "../common/nativefiledialog/src/nfd_common.c", + "../common/nativefiledialog/src/nfd_win.cpp" } + end + if os.is("Linux") then + files { + "../common/OpenGLWindow/X11OpenGLWindow.cpp", + "../common/OpenGLWindow/X11OpenGLWindows.h" + } + links {"X11", "pthread", "dl"} + if _OPTIONS["with-gtk3nfd"] then + defines { "USE_NATIVEFILEDIALOG" } + includedirs { "./../common/nativefiledialog/src/include" } + files { "../common/nativefiledialog/src/nfd_gtk.c", + "../common/nativefiledialog/src/nfd_common.c" + } + buildoptions { "`pkg-config --cflags gtk+-3.0`" } + linkoptions { "`pkg-config --libs gtk+-3.0`" } + end + end + if os.is("MacOSX") then + defines { "USE_NATIVEFILEDIALOG" } + links {"Cocoa.framework"} + files { + "../common/OpenGLWindow/MacOpenGLWindow.h", + "../common/OpenGLWindow/MacOpenGLWindow.mm", + } + includedirs { "./../common/nativefiledialog/src/include" } + files { "../common/nativefiledialog/src/nfd_cocoa.m", + "../common/nativefiledialog/src/nfd_common.c" } + end + + configuration "Debug" + defines { "DEBUG" } -- -DDEBUG + symbols "On" + targetname "view_debug" + + configuration "Release" + -- defines { "NDEBUG" } -- -NDEBUG + symbols "On" + optimize "On" + targetname "view" diff --git a/examples/raytrace/render-config.cc b/examples/raytrace/render-config.cc new file mode 100644 index 0000000..b2e8e5f --- /dev/null +++ b/examples/raytrace/render-config.cc @@ -0,0 +1,116 @@ +#include "render-config.h" + +#include "picojson.h" + +#include +#include + +namespace example { + +bool LoadRenderConfig(example::RenderConfig* config, const char* filename) { + std::ifstream is(filename); + if (is.fail()) { + std::cerr << "Cannot open " << filename << std::endl; + return false; + } + + std::istream_iterator input(is); + std::string err; + picojson::value v; + input = picojson::parse(v, input, std::istream_iterator(), &err); + if (!err.empty()) { + std::cerr << err << std::endl; + } + + if (!v.is()) { + std::cerr << "Not a JSON object" << std::endl; + return false; + } + + picojson::object o = v.get(); + + if (o.find("obj_filename") != o.end()) { + if (o["obj_filename"].is()) { + config->obj_filename = o["obj_filename"].get(); + } + } + + if (o.find("eson_filename") != o.end()) { + if (o["eson_filename"].is()) { + config->eson_filename = o["eson_filename"].get(); + } + } + + config->scene_scale = 1.0f; + if (o.find("scene_scale") != o.end()) { + if (o["scene_scale"].is()) { + config->scene_scale = static_cast(o["scene_scale"].get()); + } + } + + config->eye[0] = 0.0f; + config->eye[1] = 0.0f; + config->eye[2] = 5.0f; + if (o.find("eye") != o.end()) { + if (o["eye"].is()) { + picojson::array arr = o["eye"].get(); + if (arr.size() == 3) { + config->eye[0] = static_cast(arr[0].get()); + config->eye[1] = static_cast(arr[1].get()); + config->eye[2] = static_cast(arr[2].get()); + } + } + } + + config->up[0] = 0.0f; + config->up[1] = 1.0f; + config->up[2] = 0.0f; + if (o.find("up") != o.end()) { + if (o["up"].is()) { + picojson::array arr = o["up"].get(); + if (arr.size() == 3) { + config->up[0] = static_cast(arr[0].get()); + config->up[1] = static_cast(arr[1].get()); + config->up[2] = static_cast(arr[2].get()); + } + } + } + + config->look_at[0] = 0.0f; + config->look_at[1] = 0.0f; + config->look_at[2] = 0.0f; + if (o.find("look_at") != o.end()) { + if (o["look_at"].is()) { + picojson::array arr = o["look_at"].get(); + if (arr.size() == 3) { + config->look_at[0] = static_cast(arr[0].get()); + config->look_at[1] = static_cast(arr[1].get()); + config->look_at[2] = static_cast(arr[2].get()); + } + } + } + + config->fov = 45.0f; + if (o.find("fov") != o.end()) { + if (o["fov"].is()) { + config->fov = static_cast(o["fov"].get()); + } + } + + config->width = 512; + if (o.find("width") != o.end()) { + if (o["width"].is()) { + config->width = static_cast(o["width"].get()); + } + } + + config->height = 512; + if (o.find("height") != o.end()) { + if (o["height"].is()) { + config->height = static_cast(o["height"].get()); + } + } + + return true; +} +} diff --git a/examples/raytrace/render-config.h b/examples/raytrace/render-config.h new file mode 100644 index 0000000..fed4c0b --- /dev/null +++ b/examples/raytrace/render-config.h @@ -0,0 +1,42 @@ +#ifndef RENDER_CONFIG_H +#define RENDER_CONFIG_H + +#include + +namespace example { + +typedef struct { + // framebuffer + int width; + int height; + + // camera + float eye[3]; + float up[3]; + float look_at[3]; + float fov; // vertical fov in degree. + + // render pass + int pass; + int max_passes; + + // For debugging. Array size = width * height * 4. + float *normalImage; + float *positionImage; + float *depthImage; + float *texcoordImage; + float *varycoordImage; + + // Scene input info + std::string obj_filename; + std::string eson_filename; + float scene_scale; + +} RenderConfig; + +/// Loads config from JSON file. +bool LoadRenderConfig(example::RenderConfig *config, const char *filename); + +} // namespace + +#endif // RENDER_CONFIG_H diff --git a/examples/raytrace/render.cc b/examples/raytrace/render.cc new file mode 100644 index 0000000..a1b47ea --- /dev/null +++ b/examples/raytrace/render.cc @@ -0,0 +1,560 @@ +/* +The MIT License (MIT) + +Copyright (c) 2015 - 2016 Light Transport Entertainment, Inc. + +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. +*/ + + +#ifdef _MSC_VER +#pragma warning(disable : 4018) +#pragma warning(disable : 4244) +#pragma warning(disable : 4189) +#pragma warning(disable : 4996) +#pragma warning(disable : 4267) +#pragma warning(disable : 4477) +#endif + +#include "render.h" + +#include // C++11 +#include +#include // C++11 +#include + +#include + +#include "../../nanort.h" +#include "matrix.h" +#include "material.h" +#include "mesh.h" + + +#include "trackball.h" + + +#ifdef WIN32 +#undef min +#undef max +#endif + +namespace example { + +// PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org +// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website) +// http://www.pcg-random.org/ +typedef struct { + unsigned long long state; + unsigned long long inc; // not used? +} pcg32_state_t; + +#define PCG32_INITIALIZER \ + { 0x853c49e6748fea9bULL, 0xda3e39cb94b95bdbULL } + +float pcg32_random(pcg32_state_t* rng) { + unsigned long long oldstate = rng->state; + rng->state = oldstate * 6364136223846793005ULL + rng->inc; + unsigned int xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u; + unsigned int rot = oldstate >> 59u; + unsigned int ret = (xorshifted >> rot) | (xorshifted << ((-static_cast(rot)) & 31)); + + return (float)((double)ret / (double)4294967296.0); +} + +void pcg32_srandom(pcg32_state_t* rng, uint64_t initstate, uint64_t initseq) { + rng->state = 0U; + rng->inc = (initseq << 1U) | 1U; + pcg32_random(rng); + rng->state += initstate; + pcg32_random(rng); +} + +const float kPI = 3.141592f; + +typedef nanort::real3 float3; + +inline float3 Lerp3(float3 v0, float3 v1, float3 v2, float u, float v) { + return (1.0f - u - v) * v0 + u * v1 + v * v2; +} + +inline void CalcNormal(float3& N, float3 v0, float3 v1, float3 v2) { + float3 v10 = v1 - v0; + float3 v20 = v2 - v0; + + N = vcross(v20, v10); + N = vnormalize(N); +} + +void BuildCameraFrame(float3* origin, float3* corner, float3* u, float3* v, + float quat[4], float eye[3], float lookat[3], float up[3], + float fov, int width, int height) { + float e[4][4]; + + Matrix::LookAt(e, eye, lookat, up); + + float r[4][4]; + build_rotmatrix(r, quat); + + float3 lo; + lo[0] = lookat[0] - eye[0]; + lo[1] = lookat[1] - eye[1]; + lo[2] = lookat[2] - eye[2]; + float dist = vlength(lo); + + float dir[3]; + dir[0] = 0.0; + dir[1] = 0.0; + dir[2] = dist; + + Matrix::Inverse(r); + + float rr[4][4]; + float re[4][4]; + float zero[3] = {0.0f, 0.0f, 0.0f}; + float localUp[3] = {0.0f, 1.0f, 0.0f}; + Matrix::LookAt(re, dir, zero, localUp); + + // translate + re[3][0] += eye[0]; // 0.0; //lo[0]; + re[3][1] += eye[1]; // 0.0; //lo[1]; + re[3][2] += (eye[2] - dist); + + // rot -> trans + Matrix::Mult(rr, r, re); + + float m[4][4]; + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + m[j][i] = rr[j][i]; + } + } + + float vzero[3] = {0.0f, 0.0f, 0.0f}; + float eye1[3]; + Matrix::MultV(eye1, m, vzero); + + float lookat1d[3]; + dir[2] = -dir[2]; + Matrix::MultV(lookat1d, m, dir); + float3 lookat1(lookat1d[0], lookat1d[1], lookat1d[2]); + + float up1d[3]; + Matrix::MultV(up1d, m, up); + + float3 up1(up1d[0], up1d[1], up1d[2]); + + // absolute -> relative + up1[0] -= eye1[0]; + up1[1] -= eye1[1]; + up1[2] -= eye1[2]; + // printf("up1(after) = %f, %f, %f\n", up1[0], up1[1], up1[2]); + + // Use original up vector + // up1[0] = up[0]; + // up1[1] = up[1]; + // up1[2] = up[2]; + + { + float flen = + (0.5f * (float)height / tanf(0.5f * (float)(fov * kPI / 180.0f))); + float3 look1; + look1[0] = lookat1[0] - eye1[0]; + look1[1] = lookat1[1] - eye1[1]; + look1[2] = lookat1[2] - eye1[2]; + // vcross(u, up1, look1); + // flip + (*u) = nanort::vcross(look1, up1); + (*u) = vnormalize((*u)); + + (*v) = vcross(look1, (*u)); + (*v) = vnormalize((*v)); + + look1 = vnormalize(look1); + look1[0] = flen * look1[0] + eye1[0]; + look1[1] = flen * look1[1] + eye1[1]; + look1[2] = flen * look1[2] + eye1[2]; + (*corner)[0] = look1[0] - 0.5f * (width * (*u)[0] + height * (*v)[0]); + (*corner)[1] = look1[1] - 0.5f * (width * (*u)[1] + height * (*v)[1]); + (*corner)[2] = look1[2] - 0.5f * (width * (*u)[2] + height * (*v)[2]); + + (*origin)[0] = eye1[0]; + (*origin)[1] = eye1[1]; + (*origin)[2] = eye1[2]; + } +} + +#if 0 // TODO(LTE): Not used method. Delete. +nanort::Ray GenerateRay(const float3& origin, const float3& corner, + const float3& du, const float3& dv, float u, + float v) { + float3 dir; + + dir[0] = (corner[0] + u * du[0] + v * dv[0]) - origin[0]; + dir[1] = (corner[1] + u * du[1] + v * dv[1]) - origin[1]; + dir[2] = (corner[2] + u * du[2] + v * dv[2]) - origin[2]; + dir = vnormalize(dir); + + float3 org; + + nanort::Ray ray; + ray.org[0] = origin[0]; + ray.org[1] = origin[1]; + ray.org[2] = origin[2]; + ray.dir[0] = dir[0]; + ray.dir[1] = dir[1]; + ray.dir[2] = dir[2]; + + return ray; +} +#endif + +void FetchTexture(const Texture &texture, float u, float v, float* col) { + int tx = u * texture.width; + int ty = (1.0f - v) * texture.height; + int idx_offset = (ty * texture.width + tx) * texture.components; + col[0] = texture.image[idx_offset + 0] / 255.f; + col[1] = texture.image[idx_offset + 1] / 255.f; + col[2] = texture.image[idx_offset + 2] / 255.f; +} + +bool Renderer::Render(float* rgba, float* aux_rgba, int* sample_counts, + float quat[4], + const nanosg::Scene> &scene, + const example::Asset &asset, + const RenderConfig& config, + std::atomic& cancelFlag, + int &_showBufferMode + ) { + //if (!gAccel.IsValid()) { + // return false; + //} + + + + + int width = config.width; + int height = config.height; + + // camera + float eye[3] = {config.eye[0], config.eye[1], config.eye[2]}; + float look_at[3] = {config.look_at[0], config.look_at[1], config.look_at[2]}; + float up[3] = {config.up[0], config.up[1], config.up[2]}; + float fov = config.fov; + float3 origin, corner, u, v; + BuildCameraFrame(&origin, &corner, &u, &v, quat, eye, look_at, up, fov, width, + height); + + auto kCancelFlagCheckMilliSeconds = 300; + + std::vector workers; + std::atomic i(0); + + uint32_t num_threads = std::max(1U, std::thread::hardware_concurrency()); + + auto startT = std::chrono::system_clock::now(); + + // Initialize RNG. + + for (auto t = 0; t < num_threads; t++) { + workers.emplace_back(std::thread([&, t]() { + pcg32_state_t rng; + pcg32_srandom(&rng, config.pass, + t); // seed = combination of render pass + thread no. + + int y = 0; + while ((y = i++) < config.height) { + auto currT = std::chrono::system_clock::now(); + + std::chrono::duration ms = currT - startT; + // Check cancel flag + if (ms.count() > kCancelFlagCheckMilliSeconds) { + if (cancelFlag) { + break; + } + } + + // draw dash line to aux buffer for progress. + // for (int x = 0; x < config.width; x++) { + // float c = (x / 8) % 2; + // aux_rgba[4*(y*config.width+x)+0] = c; + // aux_rgba[4*(y*config.width+x)+1] = c; + // aux_rgba[4*(y*config.width+x)+2] = c; + // aux_rgba[4*(y*config.width+x)+3] = 0.0f; + //} + + for (int x = 0; x < config.width; x++) { + nanort::Ray ray; + ray.org[0] = origin[0]; + ray.org[1] = origin[1]; + ray.org[2] = origin[2]; + + float u0 = pcg32_random(&rng); + float u1 = pcg32_random(&rng); + + float3 dir; + + //for modes not a "color" + if(_showBufferMode != SHOW_BUFFER_COLOR) + { + //only one pass + if(config.pass > 0) + continue; + + //to the center of pixel + u0 = 0.5f; + u1 = 0.5f; + } + + dir = corner + (float(x) + u0) * u + + (float(config.height - y - 1) + u1) * v; + dir = vnormalize(dir); + ray.dir[0] = dir[0]; + ray.dir[1] = dir[1]; + ray.dir[2] = dir[2]; + + float kFar = 1.0e+30f; + ray.min_t = 0.0f; + ray.max_t = kFar; + + + nanosg::Intersection isect; + bool hit = scene.Traverse(ray, &isect, /* cull_back_face */false); + + if (hit) { + + const std::vector &materials = asset.materials; + const std::vector &textures = asset.textures; + const Mesh &mesh = asset.meshes[isect.node_id]; + + //tigra: add default material + const Material &default_material = asset.default_material; + + float3 p; + p[0] = + ray.org[0] + isect.t * ray.dir[0]; + p[1] = + ray.org[1] + isect.t * ray.dir[1]; + p[2] = + ray.org[2] + isect.t * ray.dir[2]; + + config.positionImage[4 * (y * config.width + x) + 0] = p.x(); + config.positionImage[4 * (y * config.width + x) + 1] = p.y(); + config.positionImage[4 * (y * config.width + x) + 2] = p.z(); + config.positionImage[4 * (y * config.width + x) + 3] = 1.0f; + + config.varycoordImage[4 * (y * config.width + x) + 0] = + isect.u; + config.varycoordImage[4 * (y * config.width + x) + 1] = + isect.v; + config.varycoordImage[4 * (y * config.width + x) + 2] = 0.0f; + config.varycoordImage[4 * (y * config.width + x) + 3] = 1.0f; + + unsigned int prim_id = isect.prim_id; + + float3 N; + if (mesh.facevarying_normals.size() > 0) { + float3 n0, n1, n2; + n0[0] = mesh.facevarying_normals[9 * prim_id + 0]; + n0[1] = mesh.facevarying_normals[9 * prim_id + 1]; + n0[2] = mesh.facevarying_normals[9 * prim_id + 2]; + n1[0] = mesh.facevarying_normals[9 * prim_id + 3]; + n1[1] = mesh.facevarying_normals[9 * prim_id + 4]; + n1[2] = mesh.facevarying_normals[9 * prim_id + 5]; + n2[0] = mesh.facevarying_normals[9 * prim_id + 6]; + n2[1] = mesh.facevarying_normals[9 * prim_id + 7]; + n2[2] = mesh.facevarying_normals[9 * prim_id + 8]; + N = Lerp3(n0, n1, n2, isect.u, isect.v); + } else { + unsigned int f0, f1, f2; + f0 = mesh.faces[3 * prim_id + 0]; + f1 = mesh.faces[3 * prim_id + 1]; + f2 = mesh.faces[3 * prim_id + 2]; + + float3 v0, v1, v2; + v0[0] = mesh.vertices[3 * f0 + 0]; + v0[1] = mesh.vertices[3 * f0 + 1]; + v0[2] = mesh.vertices[3 * f0 + 2]; + v1[0] = mesh.vertices[3 * f1 + 0]; + v1[1] = mesh.vertices[3 * f1 + 1]; + v1[2] = mesh.vertices[3 * f1 + 2]; + v2[0] = mesh.vertices[3 * f2 + 0]; + v2[1] = mesh.vertices[3 * f2 + 1]; + v2[2] = mesh.vertices[3 * f2 + 2]; + CalcNormal(N, v0, v1, v2); + } + + config.normalImage[4 * (y * config.width + x) + 0] = + 0.5f * N[0] + 0.5f; + config.normalImage[4 * (y * config.width + x) + 1] = + 0.5f * N[1] + 0.5f; + config.normalImage[4 * (y * config.width + x) + 2] = + 0.5f * N[2] + 0.5f; + config.normalImage[4 * (y * config.width + x) + 3] = 1.0f; + + config.depthImage[4 * (y * config.width + x) + 0] = + isect.t; + config.depthImage[4 * (y * config.width + x) + 1] = + isect.t; + config.depthImage[4 * (y * config.width + x) + 2] = + isect.t; + config.depthImage[4 * (y * config.width + x) + 3] = 1.0f; + + float3 UV; + if (mesh.facevarying_uvs.size() > 0) { + float3 uv0, uv1, uv2; + uv0[0] = mesh.facevarying_uvs[6 * prim_id + 0]; + uv0[1] = mesh.facevarying_uvs[6 * prim_id + 1]; + uv1[0] = mesh.facevarying_uvs[6 * prim_id + 2]; + uv1[1] = mesh.facevarying_uvs[6 * prim_id + 3]; + uv2[0] = mesh.facevarying_uvs[6 * prim_id + 4]; + uv2[1] = mesh.facevarying_uvs[6 * prim_id + 5]; + + UV = Lerp3(uv0, uv1, uv2, isect.u, isect.v); + + config.texcoordImage[4 * (y * config.width + x) + 0] = UV[0]; + config.texcoordImage[4 * (y * config.width + x) + 1] = UV[1]; + } + + // Fetch texture + unsigned int material_id = + mesh.material_ids[isect.prim_id]; + + //printf("material_id=%d materials=%lld\n", material_id, materials.size()); + + float diffuse_col[3]; + + float specular_col[3]; + + //tigra: material_id is ok + if(material_id>=0 && material_id= 0) { + FetchTexture(textures[diffuse_texid], UV[0], UV[1], diffuse_col); + } else { + diffuse_col[0] = materials[material_id].diffuse[0]; + diffuse_col[1] = materials[material_id].diffuse[1]; + diffuse_col[2] = materials[material_id].diffuse[2]; + } + + int specular_texid = materials[material_id].specular_texid; + if (specular_texid >= 0) { + FetchTexture(textures[specular_texid], UV[0], UV[1], specular_col); + } else { + specular_col[0] = materials[material_id].specular[0]; + specular_col[1] = materials[material_id].specular[1]; + specular_col[2] = materials[material_id].specular[2]; + } + } + else + //tigra: wrong material_id, use default_material + { + + //printf("default_material\n"); + + diffuse_col[0] = default_material.diffuse[0]; + diffuse_col[1] = default_material.diffuse[1]; + diffuse_col[2] = default_material.diffuse[2]; + specular_col[0] = default_material.specular[0]; + specular_col[1] = default_material.specular[1]; + specular_col[2] = default_material.specular[2]; + } + + // Simple shading + float NdotV = fabsf(vdot(N, dir)); + + if (config.pass == 0) { + rgba[4 * (y * config.width + x) + 0] = NdotV * diffuse_col[0]; + rgba[4 * (y * config.width + x) + 1] = NdotV * diffuse_col[1]; + rgba[4 * (y * config.width + x) + 2] = NdotV * diffuse_col[2]; + rgba[4 * (y * config.width + x) + 3] = 1.0f; + sample_counts[y * config.width + x] = + 1; // Set 1 for the first pass + } else { // additive. + rgba[4 * (y * config.width + x) + 0] += NdotV * diffuse_col[0]; + rgba[4 * (y * config.width + x) + 1] += NdotV * diffuse_col[1]; + rgba[4 * (y * config.width + x) + 2] += NdotV * diffuse_col[2]; + rgba[4 * (y * config.width + x) + 3] += 1.0f; + sample_counts[y * config.width + x]++; + } + + } else { + { + if (config.pass == 0) { + // clear pixel + rgba[4 * (y * config.width + x) + 0] = 0.0f; + rgba[4 * (y * config.width + x) + 1] = 0.0f; + rgba[4 * (y * config.width + x) + 2] = 0.0f; + rgba[4 * (y * config.width + x) + 3] = 0.0f; + aux_rgba[4 * (y * config.width + x) + 0] = 0.0f; + aux_rgba[4 * (y * config.width + x) + 1] = 0.0f; + aux_rgba[4 * (y * config.width + x) + 2] = 0.0f; + aux_rgba[4 * (y * config.width + x) + 3] = 0.0f; + sample_counts[y * config.width + x] = + 1; // Set 1 for the first pass + } else { + sample_counts[y * config.width + x]++; + } + + // No super sampling + config.normalImage[4 * (y * config.width + x) + 0] = 0.0f; + config.normalImage[4 * (y * config.width + x) + 1] = 0.0f; + config.normalImage[4 * (y * config.width + x) + 2] = 0.0f; + config.normalImage[4 * (y * config.width + x) + 3] = 0.0f; + config.positionImage[4 * (y * config.width + x) + 0] = 0.0f; + config.positionImage[4 * (y * config.width + x) + 1] = 0.0f; + config.positionImage[4 * (y * config.width + x) + 2] = 0.0f; + config.positionImage[4 * (y * config.width + x) + 3] = 0.0f; + config.depthImage[4 * (y * config.width + x) + 0] = 0.0f; + config.depthImage[4 * (y * config.width + x) + 1] = 0.0f; + config.depthImage[4 * (y * config.width + x) + 2] = 0.0f; + config.depthImage[4 * (y * config.width + x) + 3] = 0.0f; + config.texcoordImage[4 * (y * config.width + x) + 0] = 0.0f; + config.texcoordImage[4 * (y * config.width + x) + 1] = 0.0f; + config.texcoordImage[4 * (y * config.width + x) + 2] = 0.0f; + config.texcoordImage[4 * (y * config.width + x) + 3] = 0.0f; + config.varycoordImage[4 * (y * config.width + x) + 0] = 0.0f; + config.varycoordImage[4 * (y * config.width + x) + 1] = 0.0f; + config.varycoordImage[4 * (y * config.width + x) + 2] = 0.0f; + config.varycoordImage[4 * (y * config.width + x) + 3] = 0.0f; + } + } + } + + for (int x = 0; x < config.width; x++) { + aux_rgba[4 * (y * config.width + x) + 0] = 0.0f; + aux_rgba[4 * (y * config.width + x) + 1] = 0.0f; + aux_rgba[4 * (y * config.width + x) + 2] = 0.0f; + aux_rgba[4 * (y * config.width + x) + 3] = 0.0f; + } + } + })); + } + + for (auto& t : workers) { + t.join(); + } + + return (!cancelFlag); +}; + +} // namespace example diff --git a/examples/raytrace/render.h b/examples/raytrace/render.h new file mode 100644 index 0000000..ccd2f2d --- /dev/null +++ b/examples/raytrace/render.h @@ -0,0 +1,45 @@ +#ifndef EXAMPLE_RENDER_H_ +#define EXAMPLE_RENDER_H_ + +#include // C++11 + +//mode definitions now here + +#define SHOW_BUFFER_COLOR (0) +#define SHOW_BUFFER_NORMAL (1) +#define SHOW_BUFFER_POSITION (2) +#define SHOW_BUFFER_DEPTH (3) +#define SHOW_BUFFER_TEXCOORD (4) +#define SHOW_BUFFER_VARYCOORD (5) + +#include "render-config.h" +#include "nanosg.h" +#include "mesh.h" +#include "material.h" + +namespace example { + +struct Asset { + std::vector > meshes; + std::vector materials; + + //tigra: add default material + Material default_material; + std::vector textures; +}; + +class Renderer { + public: + Renderer() {} + ~Renderer() {} + + /// Returns false when the rendering was canceled. + static bool Render(float* rgba, float* aux_rgba, int *sample_counts, float quat[4], + const nanosg::Scene> &scene, const Asset &asset, const RenderConfig& config, + std::atomic& cancel_flag, + int& _showBufferMode + ); +}; +}; + +#endif // EXAMPLE_RENDER_H_ diff --git a/examples/raytrace/viwewer.make b/examples/raytrace/viwewer.make new file mode 100644 index 0000000..e341427 --- /dev/null +++ b/examples/raytrace/viwewer.make @@ -0,0 +1,339 @@ +# GNU Make project makefile autogenerated by Premake + +ifndef config + config=release_native +endif + +ifndef verbose + SILENT = @ +endif + +.PHONY: clean prebuild prelink + +ifeq ($(config),release_native) + RESCOMP = windres + TARGETDIR = bin/native/Release + TARGET = $(TARGETDIR)/view + OBJDIR = obj/native/Release + DEFINES += -DGLEW_INIT_OPENGL11_FUNCTIONS=1 -DGLEW_STATIC -DGLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS=1 + INCLUDES += -I../common/ThirdPartyLibs/Glew -I. -I../.. -I../common -I../common/imgui -I../common/glm + FORCE_INCLUDE += + ALL_CPPFLAGS += $(CPPFLAGS) -MMD -MP $(DEFINES) $(INCLUDES) + ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -O2 -g + ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS) -O2 -g -std=c++11 + ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) + LIBS += -lX11 -lpthread -ldl + LDDEPS += + ALL_LDFLAGS += $(LDFLAGS) + LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) + define PREBUILDCMDS + endef + define PRELINKCMDS + endef + define POSTBUILDCMDS + endef +all: prebuild prelink $(TARGET) + @: + +endif + +ifeq ($(config),release_x64) + RESCOMP = windres + TARGETDIR = bin/x64/Release + TARGET = $(TARGETDIR)/view + OBJDIR = obj/x64/Release + DEFINES += -DGLEW_INIT_OPENGL11_FUNCTIONS=1 -DGLEW_STATIC -DGLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS=1 + INCLUDES += -I../common/ThirdPartyLibs/Glew -I. -I../.. -I../common -I../common/imgui -I../common/glm + FORCE_INCLUDE += + ALL_CPPFLAGS += $(CPPFLAGS) -MMD -MP $(DEFINES) $(INCLUDES) + ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m64 -O2 -g + ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS) -m64 -O2 -g -std=c++11 + ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) + LIBS += -lX11 -lpthread -ldl + LDDEPS += + ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib64 -m64 + LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) + define PREBUILDCMDS + endef + define PRELINKCMDS + endef + define POSTBUILDCMDS + endef +all: prebuild prelink $(TARGET) + @: + +endif + +ifeq ($(config),release_x32) + RESCOMP = windres + TARGETDIR = bin/x32/Release + TARGET = $(TARGETDIR)/view + OBJDIR = obj/x32/Release + DEFINES += -DGLEW_INIT_OPENGL11_FUNCTIONS=1 -DGLEW_STATIC -DGLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS=1 + INCLUDES += -I../common/ThirdPartyLibs/Glew -I. -I../.. -I../common -I../common/imgui -I../common/glm + FORCE_INCLUDE += + ALL_CPPFLAGS += $(CPPFLAGS) -MMD -MP $(DEFINES) $(INCLUDES) + ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m32 -O2 -g + ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS) -m32 -O2 -g -std=c++11 + ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) + LIBS += -lX11 -lpthread -ldl + LDDEPS += + ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib32 -m32 + LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) + define PREBUILDCMDS + endef + define PRELINKCMDS + endef + define POSTBUILDCMDS + endef +all: prebuild prelink $(TARGET) + @: + +endif + +ifeq ($(config),debug_native) + RESCOMP = windres + TARGETDIR = bin/native/Debug + TARGET = $(TARGETDIR)/view_debug + OBJDIR = obj/native/Debug + DEFINES += -DGLEW_INIT_OPENGL11_FUNCTIONS=1 -DGLEW_STATIC -DGLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS=1 -DDEBUG + INCLUDES += -I../common/ThirdPartyLibs/Glew -I. -I../.. -I../common -I../common/imgui -I../common/glm + FORCE_INCLUDE += + ALL_CPPFLAGS += $(CPPFLAGS) -MMD -MP $(DEFINES) $(INCLUDES) + ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -g + ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS) -g -std=c++11 + ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) + LIBS += -lX11 -lpthread -ldl + LDDEPS += + ALL_LDFLAGS += $(LDFLAGS) + LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) + define PREBUILDCMDS + endef + define PRELINKCMDS + endef + define POSTBUILDCMDS + endef +all: prebuild prelink $(TARGET) + @: + +endif + +ifeq ($(config),debug_x64) + RESCOMP = windres + TARGETDIR = bin/x64/Debug + TARGET = $(TARGETDIR)/view_debug + OBJDIR = obj/x64/Debug + DEFINES += -DGLEW_INIT_OPENGL11_FUNCTIONS=1 -DGLEW_STATIC -DGLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS=1 -DDEBUG + INCLUDES += -I../common/ThirdPartyLibs/Glew -I. -I../.. -I../common -I../common/imgui -I../common/glm + FORCE_INCLUDE += + ALL_CPPFLAGS += $(CPPFLAGS) -MMD -MP $(DEFINES) $(INCLUDES) + ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m64 -g + ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS) -m64 -g -std=c++11 + ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) + LIBS += -lX11 -lpthread -ldl + LDDEPS += + ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib64 -m64 + LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) + define PREBUILDCMDS + endef + define PRELINKCMDS + endef + define POSTBUILDCMDS + endef +all: prebuild prelink $(TARGET) + @: + +endif + +ifeq ($(config),debug_x32) + RESCOMP = windres + TARGETDIR = bin/x32/Debug + TARGET = $(TARGETDIR)/view_debug + OBJDIR = obj/x32/Debug + DEFINES += -DGLEW_INIT_OPENGL11_FUNCTIONS=1 -DGLEW_STATIC -DGLEW_DYNAMIC_LOAD_ALL_GLX_FUNCTIONS=1 -DDEBUG + INCLUDES += -I../common/ThirdPartyLibs/Glew -I. -I../.. -I../common -I../common/imgui -I../common/glm + FORCE_INCLUDE += + ALL_CPPFLAGS += $(CPPFLAGS) -MMD -MP $(DEFINES) $(INCLUDES) + ALL_CFLAGS += $(CFLAGS) $(ALL_CPPFLAGS) -m32 -g + ALL_CXXFLAGS += $(CXXFLAGS) $(ALL_CPPFLAGS) -m32 -g -std=c++11 + ALL_RESFLAGS += $(RESFLAGS) $(DEFINES) $(INCLUDES) + LIBS += -lX11 -lpthread -ldl + LDDEPS += + ALL_LDFLAGS += $(LDFLAGS) -L/usr/lib32 -m32 + LINKCMD = $(CXX) -o "$@" $(OBJECTS) $(RESOURCES) $(ALL_LDFLAGS) $(LIBS) + define PREBUILDCMDS + endef + define PRELINKCMDS + endef + define POSTBUILDCMDS + endef +all: prebuild prelink $(TARGET) + @: + +endif + +OBJECTS := \ + $(OBJDIR)/X11OpenGLWindow.o \ + $(OBJDIR)/glew.o \ + $(OBJDIR)/ImGuizmo.o \ + $(OBJDIR)/imgui.o \ + $(OBJDIR)/imgui_draw.o \ + $(OBJDIR)/imgui_impl_btgui.o \ + $(OBJDIR)/trackball.o \ + $(OBJDIR)/main.o \ + $(OBJDIR)/matrix.o \ + $(OBJDIR)/obj-loader.o \ + $(OBJDIR)/render-config.o \ + $(OBJDIR)/render.o \ + +RESOURCES := \ + +CUSTOMFILES := \ + +SHELLTYPE := msdos +ifeq (,$(ComSpec)$(COMSPEC)) + SHELLTYPE := posix +endif +ifeq (/bin,$(findstring /bin,$(SHELL))) + SHELLTYPE := posix +endif + +$(TARGET): $(GCH) ${CUSTOMFILES} $(OBJECTS) $(LDDEPS) $(RESOURCES) + @echo Linking viwewer +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(TARGETDIR) +else + $(SILENT) mkdir $(subst /,\\,$(TARGETDIR)) +endif + $(SILENT) $(LINKCMD) + $(POSTBUILDCMDS) + +clean: + @echo Cleaning viwewer +ifeq (posix,$(SHELLTYPE)) + $(SILENT) rm -f $(TARGET) + $(SILENT) rm -rf $(OBJDIR) +else + $(SILENT) if exist $(subst /,\\,$(TARGET)) del $(subst /,\\,$(TARGET)) + $(SILENT) if exist $(subst /,\\,$(OBJDIR)) rmdir /s /q $(subst /,\\,$(OBJDIR)) +endif + +prebuild: + $(PREBUILDCMDS) + +prelink: + $(PRELINKCMDS) + +ifneq (,$(PCH)) +$(OBJECTS): $(GCH) $(PCH) +$(GCH): $(PCH) + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) -x c++-header $(ALL_CXXFLAGS) -o "$@" -MF "$(@:%.gch=%.d)" -c "$<" +endif + +$(OBJDIR)/X11OpenGLWindow.o: ../common/OpenGLWindow/X11OpenGLWindow.cpp + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/glew.o: ../common/ThirdPartyLibs/Glew/glew.c + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CC) $(ALL_CFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/ImGuizmo.o: ../common/imgui/ImGuizmo.cpp + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/imgui.o: ../common/imgui/imgui.cpp + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/imgui_draw.o: ../common/imgui/imgui_draw.cpp + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/imgui_impl_btgui.o: ../common/imgui/imgui_impl_btgui.cpp + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/trackball.o: ../common/trackball.cc + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/main.o: main.cc + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/matrix.o: matrix.cc + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/obj-loader.o: obj-loader.cc + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/render-config.o: render-config.cc + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" +$(OBJDIR)/render.o: render.cc + @echo $(notdir $<) +ifeq (posix,$(SHELLTYPE)) + $(SILENT) mkdir -p $(OBJDIR) +else + $(SILENT) mkdir $(subst /,\\,$(OBJDIR)) +endif + $(SILENT) $(CXX) $(ALL_CXXFLAGS) $(FORCE_INCLUDE) -o "$@" -MF "$(@:%.o=%.d)" -c "$<" + +-include $(OBJECTS:%.o=%.d) +ifneq (,$(PCH)) + -include $(OBJDIR)/$(notdir $(PCH)).d +endif \ No newline at end of file