From 28088760b9054ddc84f27bac0bf4437d6ac75cbb Mon Sep 17 00:00:00 2001 From: Emmanuel Benoit Date: Sat, 30 Sep 2017 10:37:45 +0200 Subject: [PATCH] "Blank" window --- .gitignore | 2 +- .vim.local/ycm_extra_conf.py | 2 - Makefile | 39 ++-- externals.hh | 32 +++ imgui_impl_sdl.cc | 282 ++++++++++++++++++++++++++ imgui_impl_sdl.h | 20 ++ main.cc | 179 +++++++++++++++++ utilities.cc | 379 +++++++++++++++++++++++++++++++++++ utilities.hh | 166 +++++++++++++++ 9 files changed, 1073 insertions(+), 28 deletions(-) create mode 100644 externals.hh create mode 100644 imgui_impl_sdl.cc create mode 100644 imgui_impl_sdl.h create mode 100644 main.cc create mode 100644 utilities.cc create mode 100644 utilities.hh diff --git a/.gitignore b/.gitignore index 3d33258..07056f0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -editor +demo *.o fd-*.h imgui.ini diff --git a/.vim.local/ycm_extra_conf.py b/.vim.local/ycm_extra_conf.py index 1567cb3..bb8b71c 100644 --- a/.vim.local/ycm_extra_conf.py +++ b/.vim.local/ycm_extra_conf.py @@ -46,9 +46,7 @@ flags = [ 'c++', '-I','.', '-I','imgui', - '-I','imguifs', '-I','glm/glm', - '-I','picojson', '-I','/usr/include/SDL2' ] diff --git a/Makefile b/Makefile index cb5ffe7..00327a3 100644 --- a/Makefile +++ b/Makefile @@ -1,36 +1,29 @@ CXXFLAGS += $(shell sdl2-config --cflags) -std=c++14 -Wall CFLAGS += $(shell sdl2-config --cflags) -CPPFLAGS += -I. -Iimgui -Iimguifs -Iglm -Ipicojson \ +CPPFLAGS += -I. -Iimgui -Iglm \ -DREAL_BUILD -DGLM_ENABLE_EXPERIMENTAL LIBS += $(shell sdl2-config --libs) -lGL -lGLEW -ldl -FILEDUMPS = \ - fd-raymarcher.glsl.h +FILEDUMPS = -IMGUI = imgui.o imgui_demo.o imgui_draw.o imguifs.o -EDITOR = \ - editor.o \ +IMGUI = imgui.o imgui_demo.o imgui_draw.o +DEMO = \ + main.o \ imgui_impl_sdl.o \ - utilities.o \ - project.o \ - \ - mg-none.o \ - mg-fragment.o \ - \ - gfx-raymarcher.o + utilities.o -EDITOR_DEPS = $(EDITOR:%.o=.%.d) +DEMO_DEPS = $(DEMO:%.o=.%.d) -editor: $(EDITOR) $(IMGUI) - $(CXX) $(CXXFLAGS) $(CPPFLAGS) -o editor \ - $(EDITOR) $(IMGUI) $(LIBS) +demo: $(DEMO) $(IMGUI) + $(CXX) $(CXXFLAGS) $(CPPFLAGS) -o demo \ + $(DEMO) $(IMGUI) $(LIBS) clean: - rm -f $(IMGUI) $(EDITOR) $(FILEDUMPS) editor externals.hh.gch + rm -f $(IMGUI) $(DEMO) $(FILEDUMPS) demo externals.hh.gch fullclean: clean - rm -f $(EDITOR_DEPS) + rm -f $(DEMO_DEPS) .PHONY: clean fullclean @@ -48,13 +41,9 @@ imgui_demo.o: imgui/imgui_demo.cpp imgui_draw.o: imgui/imgui_draw.cpp $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< -imguifs.o: imguifs/imguifilesystem.cpp imguifs/dirent_portable.h imguifs/imguifilesystem.h - $(CXX) $(CXXFLAGS) $(CPPFLAGS) -include /usr/include/stdlib.h -c -o $@ $< +-include $(DEMO_DEPS) - --include $(EDITOR_DEPS) - -$(EDITOR): %.o: %.cc externals.hh.gch | $(FILEDUMPS) +$(DEMO): %.o: %.cc externals.hh.gch | $(FILEDUMPS) $(CXX) $(CXXFLAGS) $(CPPFLAGS) -c -o $@ $< $(CXX) $(CXXFLAGS) $(CPPFLAGS) -M -MF $(@:%.o=.%.d) -MT $@ $< diff --git a/externals.hh b/externals.hh new file mode 100644 index 0000000..1e4f849 --- /dev/null +++ b/externals.hh @@ -0,0 +1,32 @@ +// C +#include + +// System (C) +#include +#include +#include +#include + +// Misc (C) +#include +#include + +// C++ std +#include +#include +#include +#include +#include +#include + +// ImGui +#include + +// GLM +#include +#include + +// Silly decoration macros I use everywhere +#define __rd__ +#define __wr__ +#define __rw__ diff --git a/imgui_impl_sdl.cc b/imgui_impl_sdl.cc new file mode 100644 index 0000000..c089c6f --- /dev/null +++ b/imgui_impl_sdl.cc @@ -0,0 +1,282 @@ +// ImGui SDL2 binding with OpenGL +// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID in imgui.cpp. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +#include "externals.hh" +#include "imgui_impl_sdl.h" + +// Data +static double g_Time = 0.0f; +static bool g_MousePressed[3] = { false, false, false }; +static float g_MouseWheel = 0.0f; +static GLuint g_FontTexture = 0; + +// This is the main rendering function that you have to implement and provide to ImGui (via setting up 'RenderDrawListsFn' in the ImGuiIO structure) +// If text or lines are blurry when integrating ImGui in your engine: +// - in your Render function, try translating your projection matrix by (0.5f,0.5f) or (0.375f,0.375f) +void ImGui_ImplSdl_RenderDrawLists(ImDrawData* draw_data) +{ + // Avoid rendering when minimized, scale coordinates for retina displays (screen coordinates != framebuffer coordinates) + ImGuiIO& io = ImGui::GetIO(); + int fb_width = (int)(io.DisplaySize.x * io.DisplayFramebufferScale.x); + int fb_height = (int)(io.DisplaySize.y * io.DisplayFramebufferScale.y); + if (fb_width == 0 || fb_height == 0) + return; + draw_data->ScaleClipRects(io.DisplayFramebufferScale); + + // We are using the OpenGL fixed pipeline to make the example code simpler to read! + // Setup render state: alpha-blending enabled, no face culling, no depth testing, scissor enabled, vertex/texcoord/color pointers. + GLint last_texture; glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + GLint last_viewport[4]; glGetIntegerv(GL_VIEWPORT, last_viewport); + GLint last_scissor_box[4]; glGetIntegerv(GL_SCISSOR_BOX, last_scissor_box); + glPushAttrib(GL_ENABLE_BIT | GL_COLOR_BUFFER_BIT | GL_TRANSFORM_BIT); + glEnable(GL_BLEND); + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + glDisable(GL_CULL_FACE); + glDisable(GL_DEPTH_TEST); + glEnable(GL_SCISSOR_TEST); + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnable(GL_TEXTURE_2D); + //glUseProgram(0); // You may want this if using this code in an OpenGL 3+ context + + // Setup viewport, orthographic projection matrix + glViewport(0, 0, (GLsizei)fb_width, (GLsizei)fb_height); + glMatrixMode(GL_PROJECTION); + glPushMatrix(); + glLoadIdentity(); + glOrtho(0.0f, io.DisplaySize.x, io.DisplaySize.y, 0.0f, -1.0f, +1.0f); + glMatrixMode(GL_MODELVIEW); + glPushMatrix(); + glLoadIdentity(); + + // Render command lists + #define OFFSETOF(TYPE, ELEMENT) ((size_t)&(((TYPE *)0)->ELEMENT)) + for (int n = 0; n < draw_data->CmdListsCount; n++) + { + const ImDrawList* cmd_list = draw_data->CmdLists[n]; + const ImDrawVert* vtx_buffer = cmd_list->VtxBuffer.Data; + const ImDrawIdx* idx_buffer = cmd_list->IdxBuffer.Data; + glVertexPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + OFFSETOF(ImDrawVert, pos))); + glTexCoordPointer(2, GL_FLOAT, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + OFFSETOF(ImDrawVert, uv))); + glColorPointer(4, GL_UNSIGNED_BYTE, sizeof(ImDrawVert), (const GLvoid*)((const char*)vtx_buffer + OFFSETOF(ImDrawVert, col))); + + for (int cmd_i = 0; cmd_i < cmd_list->CmdBuffer.Size; cmd_i++) + { + const ImDrawCmd* pcmd = &cmd_list->CmdBuffer[cmd_i]; + if (pcmd->UserCallback) + { + pcmd->UserCallback(cmd_list, pcmd); + } + else + { + glBindTexture(GL_TEXTURE_2D, (GLuint)(intptr_t)pcmd->TextureId); + glScissor((int)pcmd->ClipRect.x, (int)(fb_height - pcmd->ClipRect.w), (int)(pcmd->ClipRect.z - pcmd->ClipRect.x), (int)(pcmd->ClipRect.w - pcmd->ClipRect.y)); + glDrawElements(GL_TRIANGLES, (GLsizei)pcmd->ElemCount, sizeof(ImDrawIdx) == 2 ? GL_UNSIGNED_SHORT : GL_UNSIGNED_INT, idx_buffer); + } + idx_buffer += pcmd->ElemCount; + } + } + #undef OFFSETOF + + // Restore modified state + glDisableClientState(GL_COLOR_ARRAY); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); + glDisableClientState(GL_VERTEX_ARRAY); + glBindTexture(GL_TEXTURE_2D, (GLuint)last_texture); + glMatrixMode(GL_MODELVIEW); + glPopMatrix(); + glMatrixMode(GL_PROJECTION); + glPopMatrix(); + glPopAttrib(); + glViewport(last_viewport[0], last_viewport[1], (GLsizei)last_viewport[2], (GLsizei)last_viewport[3]); + glScissor(last_scissor_box[0], last_scissor_box[1], (GLsizei)last_scissor_box[2], (GLsizei)last_scissor_box[3]); +} + +static const char* ImGui_ImplSdl_GetClipboardText(void*) +{ + return SDL_GetClipboardText(); +} + +static void ImGui_ImplSdl_SetClipboardText(void*, const char* text) +{ + SDL_SetClipboardText(text); +} + +bool ImGui_ImplSdl_ProcessEvent(SDL_Event* event) +{ + ImGuiIO& io = ImGui::GetIO(); + switch (event->type) + { + case SDL_MOUSEWHEEL: + { + if (event->wheel.y > 0) + g_MouseWheel = 1; + if (event->wheel.y < 0) + g_MouseWheel = -1; + return true; + } + case SDL_MOUSEBUTTONDOWN: + { + if (event->button.button == SDL_BUTTON_LEFT) g_MousePressed[0] = true; + if (event->button.button == SDL_BUTTON_RIGHT) g_MousePressed[1] = true; + if (event->button.button == SDL_BUTTON_MIDDLE) g_MousePressed[2] = true; + return true; + } + case SDL_TEXTINPUT: + { + io.AddInputCharactersUTF8(event->text.text); + return true; + } + case SDL_KEYDOWN: + case SDL_KEYUP: + { + int key = event->key.keysym.sym & ~SDLK_SCANCODE_MASK; + io.KeysDown[key] = (event->type == SDL_KEYDOWN); + io.KeyShift = ((SDL_GetModState() & KMOD_SHIFT) != 0); + io.KeyCtrl = ((SDL_GetModState() & KMOD_CTRL) != 0); + io.KeyAlt = ((SDL_GetModState() & KMOD_ALT) != 0); + io.KeySuper = ((SDL_GetModState() & KMOD_GUI) != 0); + return true; + } + } + return false; +} + +bool ImGui_ImplSdl_CreateDeviceObjects() +{ + // Build texture atlas + ImGuiIO& io = ImGui::GetIO(); + unsigned char* pixels; + int width, height; + io.Fonts->GetTexDataAsAlpha8(&pixels, &width, &height); + + // Upload texture to graphics system + GLint last_texture; + glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture); + glGenTextures(1, &g_FontTexture); + glBindTexture(GL_TEXTURE_2D, g_FontTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glPixelStorei(GL_UNPACK_ROW_LENGTH, 0); + glTexImage2D(GL_TEXTURE_2D, 0, GL_ALPHA, width, height, 0, GL_ALPHA, GL_UNSIGNED_BYTE, pixels); + + // Store our identifier + io.Fonts->TexID = (void *)(intptr_t)g_FontTexture; + + // Restore state + glBindTexture(GL_TEXTURE_2D, last_texture); + + return true; +} + +void ImGui_ImplSdl_InvalidateDeviceObjects() +{ + if (g_FontTexture) + { + glDeleteTextures(1, &g_FontTexture); + ImGui::GetIO().Fonts->TexID = 0; + g_FontTexture = 0; + } +} + +bool ImGui_ImplSdl_Init(SDL_Window* window) +{ + ImGuiIO& io = ImGui::GetIO(); + io.KeyMap[ImGuiKey_Tab] = SDLK_TAB; // Keyboard mapping. ImGui will use those indices to peek into the io.KeyDown[] array. + io.KeyMap[ImGuiKey_LeftArrow] = SDL_SCANCODE_LEFT; + io.KeyMap[ImGuiKey_RightArrow] = SDL_SCANCODE_RIGHT; + io.KeyMap[ImGuiKey_UpArrow] = SDL_SCANCODE_UP; + io.KeyMap[ImGuiKey_DownArrow] = SDL_SCANCODE_DOWN; + io.KeyMap[ImGuiKey_PageUp] = SDL_SCANCODE_PAGEUP; + io.KeyMap[ImGuiKey_PageDown] = SDL_SCANCODE_PAGEDOWN; + io.KeyMap[ImGuiKey_Home] = SDL_SCANCODE_HOME; + io.KeyMap[ImGuiKey_End] = SDL_SCANCODE_END; + io.KeyMap[ImGuiKey_Delete] = SDLK_DELETE; + io.KeyMap[ImGuiKey_Backspace] = SDLK_BACKSPACE; + io.KeyMap[ImGuiKey_Enter] = SDLK_RETURN; + io.KeyMap[ImGuiKey_Escape] = SDLK_ESCAPE; + io.KeyMap[ImGuiKey_A] = SDLK_a; + io.KeyMap[ImGuiKey_C] = SDLK_c; + io.KeyMap[ImGuiKey_V] = SDLK_v; + io.KeyMap[ImGuiKey_X] = SDLK_x; + io.KeyMap[ImGuiKey_Y] = SDLK_y; + io.KeyMap[ImGuiKey_Z] = SDLK_z; + + io.RenderDrawListsFn = ImGui_ImplSdl_RenderDrawLists; // Alternatively you can set this to NULL and call ImGui::GetDrawData() after ImGui::Render() to get the same ImDrawData pointer. + io.SetClipboardTextFn = ImGui_ImplSdl_SetClipboardText; + io.GetClipboardTextFn = ImGui_ImplSdl_GetClipboardText; + io.ClipboardUserData = NULL; + +#ifdef _WIN32 + SDL_SysWMinfo wmInfo; + SDL_VERSION(&wmInfo.version); + SDL_GetWindowWMInfo(window, &wmInfo); + io.ImeWindowHandle = wmInfo.info.win.window; +#else + (void)window; +#endif + + return true; +} + +void ImGui_ImplSdl_Shutdown() +{ + ImGui_ImplSdl_InvalidateDeviceObjects(); + ImGui::Shutdown(); +} + +void ImGui_ImplSdl_NewFrame(SDL_Window *window , + bool mouseLock,ImVec2 const& mousePos) +{ + if (!g_FontTexture) + ImGui_ImplSdl_CreateDeviceObjects(); + + ImGuiIO& io = ImGui::GetIO(); + + // Setup display size (every frame to accommodate for window resizing) + int w, h; + int display_w, display_h; + SDL_GetWindowSize(window, &w, &h); + SDL_GL_GetDrawableSize(window, &display_w, &display_h); + io.DisplaySize = ImVec2((float)w, (float)h); + io.DisplayFramebufferScale = ImVec2(w > 0 ? ((float)display_w / w) : 0, h > 0 ? ((float)display_h / h) : 0); + + // Setup time step + Uint32 time = SDL_GetTicks(); + double current_time = time / 1000.0; + io.DeltaTime = g_Time > 0.0 ? (float)(current_time - g_Time) : (float)(1.0f/60.0f); + g_Time = current_time; + + // Setup inputs + // (we already got mouse wheel, keyboard keys & characters from SDL_PollEvent()) + int mx, my; + Uint32 mouseMask = SDL_GetMouseState(&mx, &my); + if ( mouseLock ) { + io.MousePos = mousePos; + } else { + if (SDL_GetWindowFlags(window) & SDL_WINDOW_MOUSE_FOCUS) + io.MousePos = ImVec2((float)mx, (float)my); // Mouse position, in pixels (set to -1,-1 if no mouse / on another screen, etc.) + else + io.MousePos = ImVec2(-1,-1); + } + + io.MouseDown[0] = g_MousePressed[0] || (mouseMask & SDL_BUTTON(SDL_BUTTON_LEFT)) != 0; // If a mouse press event came, always pass it as "mouse held this frame", so we don't miss click-release events that are shorter than 1 frame. + io.MouseDown[1] = g_MousePressed[1] || (mouseMask & SDL_BUTTON(SDL_BUTTON_RIGHT)) != 0; + io.MouseDown[2] = g_MousePressed[2] || (mouseMask & SDL_BUTTON(SDL_BUTTON_MIDDLE)) != 0; + g_MousePressed[0] = g_MousePressed[1] = g_MousePressed[2] = false; + + io.MouseWheel = g_MouseWheel; + g_MouseWheel = 0.0f; + + // Hide OS mouse cursor if ImGui is drawing it + SDL_ShowCursor(io.MouseDrawCursor ? 0 : 1); + + // Start the frame + ImGui::NewFrame(); +} diff --git a/imgui_impl_sdl.h b/imgui_impl_sdl.h new file mode 100644 index 0000000..07fe809 --- /dev/null +++ b/imgui_impl_sdl.h @@ -0,0 +1,20 @@ +// ImGui SDL2 binding with OpenGL +// In this binding, ImTextureID is used to store an OpenGL 'GLuint' texture identifier. Read the FAQ about ImTextureID in imgui.cpp. + +// You can copy and use unmodified imgui_impl_* files in your project. See main.cpp for an example of using this. +// If you use this binding you'll need to call 4 functions: ImGui_ImplXXXX_Init(), ImGui_ImplXXXX_NewFrame(), ImGui::Render() and ImGui_ImplXXXX_Shutdown(). +// If you are new to ImGui, see examples/README.txt and documentation at the top of imgui.cpp. +// https://github.com/ocornut/imgui + +struct SDL_Window; +typedef union SDL_Event SDL_Event; + +IMGUI_API bool ImGui_ImplSdl_Init(SDL_Window* window); +IMGUI_API void ImGui_ImplSdl_Shutdown(); +IMGUI_API void ImGui_ImplSdl_NewFrame(SDL_Window* window, + bool mouseLock,ImVec2 const& mousePos); +IMGUI_API bool ImGui_ImplSdl_ProcessEvent(SDL_Event* event); + +// Use if you want to reset your rendering device without losing ImGui state. +IMGUI_API void ImGui_ImplSdl_InvalidateDeviceObjects(); +IMGUI_API bool ImGui_ImplSdl_CreateDeviceObjects(); diff --git a/main.cc b/main.cc new file mode 100644 index 0000000..6ad047d --- /dev/null +++ b/main.cc @@ -0,0 +1,179 @@ +#include "externals.hh" + +#include "imgui_impl_sdl.h" +#include "utilities.hh" + + +/*= T_Main ===================================================================*/ + +struct T_Main +{ + T_Main( ); + ~T_Main( ); + + void mainLoop( ); + + private: + const std::string projectFile; + SDL_Window * window; + SDL_GLContext gl; + T_FilesWatcher watcher; + std::string loadError; + + bool done = false; + bool capture = false; + ImVec2 mouseInitial; + ImVec2 mouseMove; + + void startIteration( ); + void handleCapture( ); + void makeUI( ); + void render( ); +}; + +/*----------------------------------------------------------------------------*/ + +T_Main::T_Main( ) +{ + SDL_Init( SDL_INIT_VIDEO | SDL_INIT_TIMER ); + + // Setup window + SDL_GL_SetAttribute( SDL_GL_DOUBLEBUFFER , 1 ); + SDL_GL_SetAttribute( SDL_GL_DEPTH_SIZE , 24 ); + SDL_GL_SetAttribute( SDL_GL_STENCIL_SIZE , 8 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MAJOR_VERSION , 2 ); + SDL_GL_SetAttribute( SDL_GL_CONTEXT_MINOR_VERSION , 2 ); + SDL_DisplayMode current; + SDL_GetCurrentDisplayMode( 0 , ¤t ); + window = SDL_CreateWindow( "DEMO", + SDL_WINDOWPOS_CENTERED , SDL_WINDOWPOS_CENTERED , + 1280 , 720 , + SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE ); + gl = SDL_GL_CreateContext( window ); + glewInit(); + ImGui_ImplSdl_Init( window ); +} + +void T_Main::mainLoop( ) +{ + while ( !done ) { + startIteration( ); + if ( !done ) { + handleCapture( ); + makeUI( ); + watcher.check( ); + render( ); + } + } +} + +T_Main::~T_Main( ) +{ + ImGui_ImplSdl_Shutdown( ); + SDL_GL_DeleteContext( gl ); + SDL_DestroyWindow( window ); + SDL_Quit( ); +} + +/*----------------------------------------------------------------------------*/ + +void T_Main::startIteration( ) +{ + SDL_Event event; + mouseMove = ImVec2( ); + while ( SDL_PollEvent( &event ) ) { + ImGui_ImplSdl_ProcessEvent( &event ); + if ( event.type == SDL_QUIT ) { + done = true; + return; + } + + if ( capture && event.type == SDL_MOUSEMOTION ) { + mouseMove.x += event.motion.xrel; + mouseMove.y += event.motion.yrel; + } + } + + ImGui_ImplSdl_NewFrame( window , capture , mouseInitial ); + ImGui::GetIO( ).MouseDrawCursor = true; +} + +void T_Main::handleCapture( ) +{ + auto const& io( ImGui::GetIO( ) ); + const bool lmb( ImGui::IsMouseDown( 0 ) ); + const bool mb( lmb || ImGui::IsMouseDown( 1 ) ); + const bool appCanGrab( !( ImGui::IsMouseHoveringAnyWindow( ) + || io.WantCaptureMouse + || io.WantCaptureKeyboard ) ); + const bool shift( io.KeyShift ); + const bool ctrl( io.KeyCtrl ); + + if ( capture && !mb ) { + capture = false; + ImGui::CaptureMouseFromApp( false ); + SDL_SetRelativeMouseMode( SDL_FALSE ); + SDL_WarpMouseInWindow( window , + int( mouseInitial.x ) , + int( mouseInitial.y ) ); + ImGui::SetMouseCursor( ImGuiMouseCursor_Arrow ); + } else if ( capture ) { + ImGui::SetMouseCursor( ImGuiMouseCursor_Move ); + //project->handleDND( mouseMove , ctrl , shift , lmb ); + // FIXME: D'n'D + } else if ( appCanGrab && mb ) { + capture = true; + mouseInitial = ImGui::GetMousePos( ); + ImGui::CaptureMouseFromApp( true ); + SDL_SetRelativeMouseMode( SDL_TRUE ); + ImGui::SetMouseCursor( ImGuiMouseCursor_Move ); + } + + if ( ( appCanGrab || capture ) && io.MouseWheel ) { + //project->handleWheel( io.MouseWheel , ctrl , shift ); + // FIXME wheel + } +} + +void T_Main::makeUI( ) +{ +} + +void T_Main::render( ) +{ + auto const& dspSize( ImGui::GetIO( ).DisplaySize ); + glViewport( 0 , 0 , (int) dspSize.x, (int) dspSize.y ); + glClearColor( 1 , 0 , 1 , 1 ); + glClear( GL_COLOR_BUFFER_BIT ); + + // FIXME draw the fuck + //project->render( ); + + glUseProgram( 0 ); + ImGui::Render( ); + SDL_GL_SwapWindow( window ); +} + + +/*============================================================================*/ + +int main( int , char** ) +{ + T_Main m; + m.mainLoop( ); + +#if 0 + // Frame time history + const int nFrameTimes = 200; + float frameTimes[ nFrameTimes ]; + memset( frameTimes , 0 , sizeof( float ) * nFrameTimes ); +#endif +#if 0 + // Update frame time history + memmove( frameTimes , &frameTimes[ 1 ] , + ( nFrameTimes - 1 ) * sizeof( float ) ); + frameTimes[ nFrameTimes - 1 ] = 1000.0f / ImGui::GetIO( ).Framerate; +#endif + + return 0; +} diff --git a/utilities.cc b/utilities.cc new file mode 100644 index 0000000..429ebd6 --- /dev/null +++ b/utilities.cc @@ -0,0 +1,379 @@ +#include "externals.hh" +#include "utilities.hh" + + +void disableButton( ) +{ + ImGui::PushStyleColor( ImGuiCol_Button , ImColor( .3f , .3f , .3f ) ); + ImGui::PushStyleColor( ImGuiCol_ButtonHovered , ImColor( .3f , .3f , .3f ) ); + ImGui::PushStyleColor( ImGuiCol_ButtonActive , ImColor( .3f , .3f , .3f ) ); +} + +/*----------------------------------------------------------------------------*/ + +void updateAngle( + __rw__ float& initial , + __rd__ const float delta + ) +{ + initial = fmod( initial + delta + 540 , 360 ) - 180; +} + +void anglesToMatrix( + __rd__ float const* angles , + __wr__ float* matrix ) +{ + float c[3] , s[3]; + for ( int i = 0 ; i < 3 ; i ++ ) { + const float a = M_PI * angles[ i ] / 180; + c[i] = cos( a ); + s[i] = sin( a ); + } + matrix[0] = c[1]*c[2]; + matrix[1] = s[0]*s[1]*c[2] - c[0]*s[2]; + matrix[2] = s[0]*s[2] + c[0]*s[1]*c[2]; + matrix[3] = c[1]*s[2]; + matrix[4] = c[0]*c[2] + s[0]*s[1]*s[2]; + matrix[5] = c[0]*s[1]*s[2] - s[0]*c[2]; + matrix[6] = -s[1]; + matrix[7] = s[0]*c[1]; + matrix[8] = c[0]*c[1]; +} + + +/*= T_FilesWatcher ===========================================================*/ + +T_FilesWatcher::T_FilesWatcher( ) + : fd( inotify_init1( O_NONBLOCK ) ) +{ } + +T_FilesWatcher::T_FilesWatcher( T_FilesWatcher&& other ) noexcept + : fd( 0 ) , watched( std::move( other.watched ) ) +{ + std::swap( fd , other.fd ); + other.watched.clear( ); + for ( T_WatchedFiles* wf : watched ) { + if ( wf ) { + wf->watcher = this; + } + } +} + +T_FilesWatcher::~T_FilesWatcher( ) +{ + if ( fd ) { + close( fd ); + } + for ( T_WatchedFiles* wf : watched ) { + if ( wf ) { + wf->watcher = nullptr; + } + } +} + +void T_FilesWatcher::check( ) +{ + for ( T_WatchedFiles* wf : watched ) { + if ( wf ) { + wf->triggered = false; + } + } + + inotify_event ie; + while ( read( fd , &ie , sizeof( ie ) ) == sizeof( ie ) ) { + if ( ( ie.mask & ( IN_CLOSE_WRITE | IN_DELETE_SELF ) ) == 0 ) { + continue; + } + + for ( T_WatchedFiles* wf : watched ) { + if ( !wf || wf->triggered ) { + continue; + } + auto const& idl( wf->identifiers ); + if ( find( idl , ie.wd ) != idl.end( ) ) { + wf->triggered = true; + wf->callback( ); + } + } + } +} + +/*= T_WatchedFiles ===========================================================*/ + +T_WatchedFiles::T_WatchedFiles( T_WatchedFiles&& other ) noexcept + : watcher( other.watcher ) , callback( other.callback ) , + triggered( other.triggered ) , + identifiers( std::move( other.identifiers ) ) +{ + if ( watcher ) { + other.watcher = nullptr; + *( find( watcher->watched , &other ) ) = this; + } +} + +T_WatchedFiles::T_WatchedFiles( + __rw__ T_FilesWatcher& watcher , + __rd__ const F_OnFileChanges callback ) + : watcher( &watcher ) , callback( callback ) , triggered( false ) +{ + watcher.watched.push_back( this ); +} + +T_WatchedFiles::~T_WatchedFiles( ) +{ + clear( ); + if ( watcher ) { + watcher->watched.erase( find( watcher->watched , this ) ); + } +} + +void T_WatchedFiles::clear( ) +{ + if ( watcher ) { + const auto fd( watcher->fd ); + for ( int wd : identifiers ) { + inotify_rm_watch( fd , wd ); + } + } + identifiers.clear( ); +} + +bool T_WatchedFiles::watch( + __rd__ std::string const& file ) +{ + static constexpr auto inFlags( IN_CLOSE_WRITE | IN_DELETE_SELF ); + if ( watcher ) { + const auto wd( inotify_add_watch( watcher->fd , + file.c_str( ) , inFlags ) ); + if ( wd == -1 ) { + return false; + } + if ( find( identifiers , wd ) == identifiers.end( ) ) { + identifiers.push_back( wd ); + } + return true; + } + return false; +} + + +/*= T_ShaderCode =============================================================*/ + +T_ShaderCode::T_ShaderCode( + __rd__ const int nparts ) + : code( nparts , nullptr ) +{ } + +T_ShaderCode::~T_ShaderCode( ) +{ + for ( char* str : code ) { + delete[] str; + } +} + +/*----------------------------------------------------------------------------*/ + +void T_ShaderCode::setPart( + __rd__ const int index , + __rd__ char const* const string ) +{ + assert( code[ index ] == nullptr ); + + const int len( strlen( string ) + 1 ); + char buffer[ 32 ]; + const int extraLen( index == 0 ? 0 + : snprintf( buffer , sizeof( buffer ) , + "\n#line 0 %d\n" , index ) ); + + char* const output( new char[ extraLen + len ] ); + if ( index != 0 ) { + memcpy( output , buffer , extraLen ); + } + strcpy( output + extraLen , string ); + code[ index ] = output; +} + +void T_ShaderCode::setPart( + __rd__ const int index , + __rd__ void const* const data , + __rd__ const int size ) +{ + assert( code[ index ] == nullptr ); + + char buffer[ 32 ]; + const int extraLen( index == 0 ? 0 + : snprintf( buffer , sizeof( buffer ) , + "\n#line 0 %d\n" , index ) ); + + char* const output( new char[ extraLen + size + 1 ] ); + if ( index != 0 ) { + memcpy( output , buffer , extraLen ); + } + memcpy( output + extraLen , data , size ); + output[ extraLen + size ] = 0; + code[ index ] = output; +} + +bool T_ShaderCode::loadPart( + __rd__ const int index , + __rd__ std::string const& source , + __rw__ std::vector< std::string >& errors ) +{ + assert( code[ index ] == nullptr ); + + FILE * f = fopen( source.c_str( ) , "r" ); + if ( !f ) { + std::string error( "File not found: " ); + error += source; + errors.push_back( error ); + return false; + } + + char buffer[ 32 ]; + const int extraLen( index == 0 ? 0 + : snprintf( buffer , sizeof( buffer ) , + "\n#line 0 %d\n" , index ) ); + + fseek( f , 0 , SEEK_END ); + const size_t size( ftell( f ) ); + fseek( f , 0 , SEEK_SET ); + + char* const output( new char[ extraLen + size + 1 ] ); + if ( index != 0 ) { + memcpy( output , buffer , extraLen ); + } + if ( fread( output + extraLen , 1 , size , f ) != size ) { + fclose( f ); + delete[] output; + std::string error( "Could not read file: " ); + error += source; + errors.push_back( error ); + return false; + } + output[ extraLen + size ] = 0; + fclose( f ); + code[ index ] = output; + return true; +} + +/*----------------------------------------------------------------------------*/ + +GLuint T_ShaderCode::createProgram( + __rd__ GLenum type , + __rw__ std::vector< std::string >& errors ) const +{ + GLenum sid = glCreateShaderProgramv( type , code.size( ) , &code[ 0 ] ); + if ( sid == 0 ) { + errors.push_back( "Failed to create GL program" ); + return sid; + } + + int infoLogLength; + glGetProgramiv( sid , GL_INFO_LOG_LENGTH , &infoLogLength ); + if ( infoLogLength ) { + char buffer[ infoLogLength + 1 ]; + glGetProgramInfoLog( sid , infoLogLength , nullptr , buffer ); + char* start( buffer ); + char* found( strchr( buffer , '\n' ) ); + while ( found ) { + *found = 0; + errors.push_back( start ); + start = found + 1; + found = strchr( start , '\n' ); + } + if ( start < &buffer[ infoLogLength - 1 ] ) { + errors.push_back( start ); + } + } + + int lnk; + glGetProgramiv( sid , GL_LINK_STATUS , &lnk ); + if ( !lnk ) { + glDeleteProgram( sid ); + return 0; + } + + return sid; +} + + +/*= T_Camera =================================================================*/ + +void T_Camera::handleDND( + __rd__ ImVec2 const& move , + __rd__ const bool hasCtrl , + __rd__ const bool hasShift , + __rd__ const bool lmb // Left mouse button + ) +{ + if ( move.x == 0 || move.y == 0 ) { + return; + } + + const float fdx( move.x * .1f * ( hasCtrl ? 1.f : .1f ) ); + const float fdy( move.y * .1f * ( hasCtrl ? 1.f : .1f ) ); + + if ( lmb && hasShift ) { + // Left mouse button, shift - move camera + const auto side( glm::normalize( glm::cross( up , dir ) ) ); + lookAt += .01f * ( side * fdx + up * fdy ); + } else if ( lmb ) { + // Left mouse button, no shift - change yaw/pitch + updateAngle( angles.y , fdx ); + updateAngle( angles.x , fdy ); + } else { + // Right mouse button - change roll + updateAngle( angles.z , fdx ); + } + update( ); +} + +void T_Camera::handleWheel( + __rd__ const float wheel , + __rd__ const bool hasCtrl , + __rd__ const bool hasShift + ) +{ + const float delta( wheel * ( hasCtrl ? 1.f : .1f) ); + if ( hasShift ) { + fov = std::max( 1.f , std::min( 179.f , fov + delta ) ); + } else { + distance = std::max( .01f , distance - delta ); + } + update( ); +} + +/*----------------------------------------------------------------------------*/ + +void T_Camera::makeUI( ) +{ + if ( !ImGui::CollapsingHeader( "Camera" ) ) { + return; + } + + const bool changed[] = { + ImGui::DragFloat3( "Look at" , &lookAt.x ) , + ImGui::DragFloat( "Distance" , &distance , .1f , + .1f , 1e8 , "%.1f" ) , + ImGui::DragFloat3( "Angles" , &angles.x , .01f , -180 , 180 ) , + ImGui::DragFloat( "FoV" , &fov , .01f , .01f , 179.9f ) + }; + + for ( unsigned i = 0 ; i < sizeof( changed ) / sizeof( bool ) ; i ++ ) { + if ( changed[ i ] ) { + update( ); + break; + } + } +} + +/*----------------------------------------------------------------------------*/ + +void T_Camera::update( ) +{ + anglesToMatrix( &angles.x , &rotMat[ 0 ].x ); + dir = glm::vec3( 0 , 0 , -distance ) * rotMat; + up = glm::vec3( 0 , 1 , 0 ) * rotMat; + pos = lookAt - dir; + np = 2 * tan( M_PI * ( 180. - fov ) / 360. ); +} diff --git a/utilities.hh b/utilities.hh new file mode 100644 index 0000000..b1d8f7e --- /dev/null +++ b/utilities.hh @@ -0,0 +1,166 @@ +#pragma once +#ifndef REAL_BUILD +# include "externals.hh" +#endif + +/*= Utilities ================================================================*/ + +// Disable next ImGui button(s) +void disableButton( ); +// Re-enable ImGui buttons +inline void reenableButtons( ) +{ + ImGui::PopStyleColor( 3 ); +} + +/*----------------------------------------------------------------------------*/ + +// Add some value to an angle, keeping it in [-180;180] +void updateAngle( + __rw__ float& initial , + __rd__ const float delta + ); +// Make a rotation matrix from three YPR angles (in degrees) +void anglesToMatrix( + __rd__ float const* angles , + __wr__ float* matrix ); + +/*----------------------------------------------------------------------------*/ + +// Helpers for finding entries in collections +template< typename T , typename I > +inline auto find( + __rd__ T const& collection , + __rd__ I const& item ) +{ + return std::find( collection.begin( ) , collection.end( ) , item ); +} + +template< typename T , typename I > +inline auto find( + __rw__ T& collection , + __rd__ I const& item ) +{ + return std::find( collection.begin( ) , collection.end( ) , item ); +} + + +/*= T_FilesWatcher / T_WatchedFiles ==========================================*/ + +struct T_FilesWatcher; +struct T_WatchedFiles; +using F_OnFileChanges = std::function< void( void ) >; + +struct T_FilesWatcher +{ + friend struct T_WatchedFiles; + + T_FilesWatcher( T_FilesWatcher const& ) = delete; + + T_FilesWatcher( ); + T_FilesWatcher( T_FilesWatcher&& ) noexcept; + ~T_FilesWatcher( ); + + void check( ); + + private: + int fd; + std::vector< T_WatchedFiles* > watched; +}; + +/*----------------------------------------------------------------------------*/ + +struct T_WatchedFiles +{ + friend struct T_FilesWatcher; + + T_WatchedFiles( ) = delete; + T_WatchedFiles( T_WatchedFiles const& ) = delete; + + T_WatchedFiles( T_WatchedFiles&& ) noexcept; + T_WatchedFiles( + __rw__ T_FilesWatcher& watcher , + __rd__ const F_OnFileChanges callback ); + + ~T_WatchedFiles( ); + + void clear( ); + bool watch( __rd__ std::string const& file ); + + private: + T_FilesWatcher* watcher; + const F_OnFileChanges callback; + bool triggered; + std::vector< int > identifiers; +}; + + +/*= T_ShaderCode =============================================================*/ + +struct T_ShaderCode +{ + T_ShaderCode( ) = delete; + T_ShaderCode( T_ShaderCode const& ) = delete; + T_ShaderCode( T_ShaderCode&& ) = delete; + + explicit T_ShaderCode( __rd__ const int nparts ); + ~T_ShaderCode( ); + + void setPart( + __rd__ const int index , + __rd__ char const* const string ); + void setPart( + __rd__ const int index , + __rd__ void const* const data , + __rd__ const int size ); + bool loadPart( + __rd__ const int index , + __rd__ std::string const& source , + __rw__ std::vector< std::string >& errors ); + + GLuint createProgram( + __rd__ GLenum type , + __rw__ std::vector< std::string >& errors ) const; + + private: + std::vector< char* > code; +}; + + +/*= T_Camera =================================================================*/ + +struct T_Camera +{ + glm::vec3 lookAt; + glm::vec3 angles; + float distance = 10; + float fov = 90; + + // Everything below is updated by update() + glm::vec3 dir; + glm::vec3 up; + glm::vec3 pos; + float np; + + T_Camera() + { update( ); } + + void handleDND( + __rd__ ImVec2 const& move , + __rd__ const bool hasCtrl , + __rd__ const bool hasShift , + __rd__ const bool lmb // Left mouse button + ); + void handleWheel( + __rd__ const float wheel , + __rd__ const bool hasCtrl , + __rd__ const bool hasShift + ); + + void makeUI( ); + + private: + glm::mat3x3 rotMat; + float rotationMatrix[ 9 ]; + void update( ); +};