// // Copyright (c) 2009-2010 Mikko Mononen memon@inside.org // // This software is provided 'as-is', without any express or implied // warranty. In no event will the authors be held liable for any damages // arising from the use of this software. // Permission is granted to anyone to use this software for any purpose, // including commercial applications, and to alter it and redistribute it // freely, subject to the following restrictions: // 1. The origin of this software must not be misrepresented; you must not // claim that you wrote the original software. If you use this software // in a product, an acknowledgment in the product documentation would be // appreciated but is not required. // 2. Altered source versions must be plainly marked as such, and must not be // misrepresented as being the original software. // 3. This notice may not be removed or altered from any source distribution. // #include "SDL.h" #include "SDL_keycode.h" #include "SDL_opengl.h" #include #include #include #include #ifdef __APPLE__ # include #else # include #endif #include "Filelist.h" #include "InputGeom.h" #include "Recast.h" #include "Sample_SoloMesh.h" #include "Sample_TempObstacles.h" #include "Sample_TileMesh.h" #include "TestCase.h" #include #include #include #ifdef WIN32 # define snprintf _snprintf #endif using std::string; using std::vector; struct SampleItem { string name; std::function create; }; static SampleItem g_samples[] = { {"Solo Mesh", []() { return new Sample_SoloMesh(); } }, {"Tile Mesh", []() { return new Sample_TileMesh(); } }, {"Temp Obstacles", []() { return new Sample_TempObstacles(); }}, }; static constexpr int g_nsamples = sizeof(g_samples) / sizeof(SampleItem); static constexpr float fogColor[4] = {0.32f, 0.31f, 0.30f, 1.0f}; struct AppData { // Window & SDL int width; int height; SDL_Window* window; SDL_GLContext glContext; // Recast data, samples, and test cases BuildContext buildContext; InputGeom* inputGeometry = nullptr; Sample* sample = nullptr; TestCase* testCase = nullptr; // Time float timeAcc = 0.0f; Uint32 prevFrameTime = 0; // Input int mousePos[2]{0, 0}; int origMousePos[2]{0, 0}; // Used to compute mouse movement totals across frames. // Camera float cameraEulers[2]{45, -45}; float cameraPos[3] = {0, 0, 0}; float camr = 1000; float origCameraEulers[2] = {0, 0}; // Used to compute rotational changes across frames. // Movement float moveFront = 0.0f; float moveBack = 0.0f; float moveLeft = 0.0f; float moveRight = 0.0f; float moveUp = 0.0f; float moveDown = 0.0f; // Zoom float scrollZoom = 0; // Input state bool isRotatingCamera = false; bool movedDuringRotate = false; bool mouseOverMenu = false; // Raycasts float rayStart[3]; float rayEnd[3]; // UI int sampleIndex = -1; string meshName = "Choose Mesh..."; // UI state bool showMenu = true; bool showLog = false; bool showTools = true; bool showTestCases = false; // Window scroll positions. int logScroll = 0; // Files vector files; string meshesFolder = "Meshes"; string testCasesFolder = "TestCases"; // Markers float markerPosition[3] = {0, 0, 0}; bool markerPositionSet = false; void resetCamera() { const float* bmin = 0; const float* bmax = 0; if (inputGeometry) { bmin = inputGeometry->getNavMeshBoundsMin(); bmax = inputGeometry->getNavMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { camr = sqrtf(rcSqr(bmax[0] - bmin[0]) + rcSqr(bmax[1] - bmin[1]) + rcSqr(bmax[2] - bmin[2])) / 2; cameraPos[0] = (bmax[0] + bmin[0]) / 2 + camr; cameraPos[1] = (bmax[1] + bmin[1]) / 2 + camr; cameraPos[2] = (bmax[2] + bmin[2]) / 2 + camr; camr *= 3; } cameraEulers[0] = 45; cameraEulers[1] = -45; glFogf(GL_FOG_START, camr * 0.1f); glFogf(GL_FOG_END, camr * 1.25f); } }; int main(int /*argc*/, char** /*argv*/) { AppData app; // Init SDL if (SDL_Init(SDL_INIT_EVERYTHING) != 0) { printf("Could not initialise SDL.\nError: %s\n", SDL_GetError()); return -1; } // Use OpenGL render driver. SDL_SetHint(SDL_HINT_RENDER_DRIVER, "opengl"); // Enable depth buffer. SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1); SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24); // Set color channel depth. SDL_GL_SetAttribute(SDL_GL_RED_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_GREEN_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_BLUE_SIZE, 8); SDL_GL_SetAttribute(SDL_GL_ALPHA_SIZE, 8); // 4x MSAA. SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 1); SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 4); SDL_DisplayMode displayMode; SDL_GetCurrentDisplayMode(0, &displayMode); app.width = rcMin(displayMode.w, static_cast(static_cast(displayMode.h) * (16.0f / 9.0f))) - 80; app.height = displayMode.h - 80; // Create the SDL window with OpenGL support app.window = SDL_CreateWindow( "My App", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, app.width, app.height, SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE); // Create the OpenGL context app.glContext = SDL_GL_CreateContext(app.window); SDL_GL_MakeCurrent(app.window, app.glContext); if (!app.window || !app.glContext) { printf("Could not initialize SDL opengl\nError: %s\n", SDL_GetError()); return -1; } SDL_SetWindowPosition(app.window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); // Setup Dear ImGui context IMGUI_CHECKVERSION(); ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls io.Fonts->AddFontFromFileTTF("DroidSans.ttf", 16.0f); // Size in pixels ImGui::PushFont(io.Fonts->Fonts[0]); ImGui::StyleColorsDark(); // Set style float main_scale = ImGui_ImplSDL2_GetContentScaleForDisplay(0); ImGuiStyle& style = ImGui::GetStyle(); style.ScaleAllSizes(main_scale); style.FontScaleDpi = main_scale; style.WindowRounding = 8.0f; style.FrameRounding = 4.0f; style.WindowPadding = ImVec2(10, 10); style.FramePadding = ImVec2(8, 4); style.Colors[ImGuiCol_WindowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.75f); style.Colors[ImGuiCol_Header] = ImVec4(1.0f, 1.0f, 1.0f, 0.5f); style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.75f); style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(1.0f, 1.0f, 1.0f, 0.25f); style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(1.0f, 0.75f, 0, 0.75f); style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(1.0f, 0.75f, 0, 0.37f); style.Colors[ImGuiCol_Button] = ImVec4(0.5f, 0.5f, 0.5f, 96.0f / 255.0f); style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.5f, 0.5f, 0.5f, 196.0f / 255.0f); //style.Colors[ImGuiCol_ButtonHovered] = ImVec4(1.0f, 196.0f / 255.0f, 0, 96.0f / 255.0f); /* /// 0 = FLAT APPEARENCE /// 1 = MORE "3D" LOOK int is3D = 1; style.Colors[ImGuiCol_Text] = ImVec4(1.00f, 1.00f, 1.00f, 1.00f); style.Colors[ImGuiCol_TextDisabled] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f); style.Colors[ImGuiCol_TextSelectedBg] = ImVec4(0.73f, 0.73f, 0.73f, 0.35f); style.Colors[ImGuiCol_ChildBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.75f); style.Colors[ImGuiCol_WindowBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.75f); style.Colors[ImGuiCol_PopupBg] = ImVec4(0.0f, 0.0f, 0.0f, 0.75f); style.Colors[ImGuiCol_Border] = ImVec4(0.12f, 0.12f, 0.12f, 0.71f); style.Colors[ImGuiCol_BorderShadow] = ImVec4(1.00f, 1.00f, 1.00f, 0.06f); style.Colors[ImGuiCol_FrameBg] = ImVec4(0.42f, 0.42f, 0.42f, 0.54f); style.Colors[ImGuiCol_FrameBgHovered] = ImVec4(0.42f, 0.42f, 0.42f, 0.40f); style.Colors[ImGuiCol_FrameBgActive] = ImVec4(0.56f, 0.56f, 0.56f, 0.67f); style.Colors[ImGuiCol_TitleBg] = ImVec4(0.19f, 0.19f, 0.19f, 1.00f); style.Colors[ImGuiCol_TitleBgActive] = ImVec4(0.22f, 0.22f, 0.22f, 1.00f); style.Colors[ImGuiCol_TitleBgCollapsed] = ImVec4(0.17f, 0.17f, 0.17f, 0.90f); style.Colors[ImGuiCol_MenuBarBg] = ImVec4(0.335f, 0.335f, 0.335f, 1.000f); style.Colors[ImGuiCol_ScrollbarBg] = ImVec4(0.24f, 0.24f, 0.24f, 0.53f); style.Colors[ImGuiCol_ScrollbarGrab] = ImVec4(0.41f, 0.41f, 0.41f, 1.00f); style.Colors[ImGuiCol_ScrollbarGrabHovered] = ImVec4(0.52f, 0.52f, 0.52f, 1.00f); style.Colors[ImGuiCol_ScrollbarGrabActive] = ImVec4(0.76f, 0.76f, 0.76f, 1.00f); style.Colors[ImGuiCol_CheckMark] = ImVec4(0.65f, 0.65f, 0.65f, 1.00f); style.Colors[ImGuiCol_SliderGrab] = ImVec4(0.52f, 0.52f, 0.52f, 1.00f); style.Colors[ImGuiCol_SliderGrabActive] = ImVec4(0.64f, 0.64f, 0.64f, 1.00f); style.Colors[ImGuiCol_Button] = ImVec4(0.54f, 0.54f, 0.54f, 0.35f); style.Colors[ImGuiCol_ButtonHovered] = ImVec4(0.52f, 0.52f, 0.52f, 0.59f); style.Colors[ImGuiCol_ButtonActive] = ImVec4(0.76f, 0.76f, 0.76f, 1.00f); style.Colors[ImGuiCol_Header] = ImVec4(0.38f, 0.38f, 0.38f, 1.00f); style.Colors[ImGuiCol_HeaderHovered] = ImVec4(0.47f, 0.47f, 0.47f, 1.00f); style.Colors[ImGuiCol_HeaderActive] = ImVec4(0.76f, 0.76f, 0.76f, 0.77f); style.Colors[ImGuiCol_Separator] = ImVec4(0.000f, 0.000f, 0.000f, 0.137f); style.Colors[ImGuiCol_SeparatorHovered] = ImVec4(0.700f, 0.671f, 0.600f, 0.290f); style.Colors[ImGuiCol_SeparatorActive] = ImVec4(0.702f, 0.671f, 0.600f, 0.674f); style.Colors[ImGuiCol_ResizeGrip] = ImVec4(0.26f, 0.59f, 0.98f, 0.25f); style.Colors[ImGuiCol_ResizeGripHovered] = ImVec4(0.26f, 0.59f, 0.98f, 0.67f); style.Colors[ImGuiCol_ResizeGripActive] = ImVec4(0.26f, 0.59f, 0.98f, 0.95f); style.Colors[ImGuiCol_PlotLines] = ImVec4(0.61f, 0.61f, 0.61f, 1.00f); style.Colors[ImGuiCol_PlotLinesHovered] = ImVec4(1.00f, 0.43f, 0.35f, 1.00f); style.Colors[ImGuiCol_PlotHistogram] = ImVec4(0.90f, 0.70f, 0.00f, 1.00f); style.Colors[ImGuiCol_PlotHistogramHovered] = ImVec4(1.00f, 0.60f, 0.00f, 1.00f); style.Colors[ImGuiCol_ModalWindowDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.35f); style.Colors[ImGuiCol_DragDropTarget] = ImVec4(1.00f, 1.00f, 0.00f, 0.90f); style.Colors[ImGuiCol_NavHighlight] = ImVec4(0.26f, 0.59f, 0.98f, 1.00f); style.Colors[ImGuiCol_NavWindowingHighlight] = ImVec4(1.00f, 1.00f, 1.00f, 0.70f); style.Colors[ImGuiCol_NavWindowingDimBg] = ImVec4(0.80f, 0.80f, 0.80f, 0.20f); style.PopupRounding = 3; style.WindowPadding = ImVec2(4, 4); style.FramePadding = ImVec2(6, 4); style.ItemSpacing = ImVec2(6, 2); style.ScrollbarSize = 18; style.WindowBorderSize = 1; style.ChildBorderSize = 1; style.PopupBorderSize = 1; style.FrameBorderSize = is3D; style.WindowRounding = 3; style.ChildRounding = 3; style.FrameRounding = 3; style.ScrollbarRounding = 2; style.GrabRounding = 3; #ifdef IMGUI_HAS_DOCK style.TabBorderSize = is3D; style.TabRounding = 3; colors[ImGuiCol_DockingEmptyBg] = ImVec4(0.38f, 0.38f, 0.38f, 1.00f); colors[ImGuiCol_Tab] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); colors[ImGuiCol_TabHovered] = ImVec4(0.40f, 0.40f, 0.40f, 1.00f); colors[ImGuiCol_TabActive] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f); colors[ImGuiCol_TabUnfocused] = ImVec4(0.25f, 0.25f, 0.25f, 1.00f); colors[ImGuiCol_TabUnfocusedActive] = ImVec4(0.33f, 0.33f, 0.33f, 1.00f); colors[ImGuiCol_DockingPreview] = ImVec4(0.85f, 0.85f, 0.85f, 0.28f); if (ImGui::GetIO().ConfigFlags & ImGuiConfigFlags_ViewportsEnable) { style.WindowRounding = 0.0f; style.Colors[ImGuiCol_WindowBg].w = 1.0f; } #endif */ // Setup Platform/Renderer backends ImGui_ImplSDL2_InitForOpenGL(app.window, app.glContext); ImGui_ImplOpenGL2_Init(); ImGuiWindowFlags staticWindowFlags = ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoCollapse; app.prevFrameTime = SDL_GetTicks(); // Set up fog. glEnable(GL_FOG); glFogi(GL_FOG_MODE, GL_LINEAR); glFogf(GL_FOG_START, app.camr * 0.1f); glFogf(GL_FOG_END, app.camr * 1.25f); glFogfv(GL_FOG_COLOR, fogColor); // OpenGL settings glEnable(GL_CULL_FACE); glDepthFunc(GL_LEQUAL); //---------------------------------------------------------------------------- scanDirectory(app.meshesFolder, ".obj", app.files); app.meshName = app.files[0]; app.inputGeometry = new InputGeom; app.inputGeometry->load(&app.buildContext, app.meshesFolder + "/" + app.meshName); app.sampleIndex = 0; app.sample = g_samples[0].create(); app.sample->setContext(&app.buildContext); app.sample->handleMeshChanged(app.inputGeometry); // Reset camera and fog to match the mesh bounds. const float* bmin = app.inputGeometry->getNavMeshBoundsMin(); const float* bmax = app.inputGeometry->getNavMeshBoundsMax(); app.camr = sqrtf(rcSqr(bmax[0] - bmin[0]) + rcSqr(bmax[1] - bmin[1]) + rcSqr(bmax[2] - bmin[2])) / 2; app.cameraPos[0] = (bmax[0] + bmin[0]) / 2 + app.camr; app.cameraPos[1] = (bmax[1] + bmin[1]) / 2 + app.camr; app.cameraPos[2] = (bmax[2] + bmin[2]) / 2 + app.camr; app.camr *= 3; app.cameraEulers[0] = 45; app.cameraEulers[1] = -45; glFogf(GL_FOG_START, app.camr * 0.1f); glFogf(GL_FOG_END, app.camr * 1.25f); //---------------------------------------------------------------------------- bool done = false; while (!done) { // Handle input events. bool processHitTest = false; bool processHitTestShift = false; // Per frame input app.mouseOverMenu = ImGui::IsWindowHovered(ImGuiHoveredFlags_AnyWindow); SDL_Event event; while (SDL_PollEvent(&event)) { ImGui_ImplSDL2_ProcessEvent(&event); switch (event.type) { case SDL_KEYDOWN: // Handle any key presses here. switch (event.key.keysym.sym) { case SDLK_ESCAPE: done = true; break; case SDLK_t: app.showTestCases = true; app.files.clear(); scanDirectory(app.testCasesFolder, ".txt", app.files); break; case SDLK_TAB: app.showMenu = !app.showMenu; break; case SDLK_SPACE: if (app.sample) { app.sample->handleToggle(); } break; case SDLK_1: if (app.sample) { app.sample->handleStep(); } break; case SDLK_9: if (app.sample && app.inputGeometry) { BuildSettings settings; rcVcopy(settings.navMeshBMin, app.inputGeometry->getNavMeshBoundsMin()); rcVcopy(settings.navMeshBMax, app.inputGeometry->getNavMeshBoundsMax()); app.sample->collectSettings(settings); app.inputGeometry->saveGeomSet(&settings); } break; } break; case SDL_MOUSEWHEEL: if (!app.mouseOverMenu) { app.scrollZoom += static_cast(event.wheel.y); } break; case SDL_MOUSEBUTTONDOWN: if (event.button.button == SDL_BUTTON_RIGHT && !app.mouseOverMenu) { // Rotate view app.isRotatingCamera = true; app.movedDuringRotate = false; app.origMousePos[0] = app.mousePos[0]; app.origMousePos[1] = app.mousePos[1]; app.origCameraEulers[0] = app.cameraEulers[0]; app.origCameraEulers[1] = app.cameraEulers[1]; } break; case SDL_MOUSEBUTTONUP: // Handle mouse clicks here. if (event.button.button == SDL_BUTTON_RIGHT) { app.isRotatingCamera = false; if (!app.mouseOverMenu) { if (!app.movedDuringRotate) { processHitTest = true; processHitTestShift = true; } } } else if (event.button.button == SDL_BUTTON_LEFT) { if (!app.mouseOverMenu) { processHitTest = true; processHitTestShift = (SDL_GetModState() & KMOD_SHIFT) ? true : false; } } break; case SDL_MOUSEMOTION: app.mousePos[0] = event.motion.x; app.mousePos[1] = app.height - 1 - event.motion.y; if (app.isRotatingCamera) { int dx = app.mousePos[0] - app.origMousePos[0]; int dy = app.mousePos[1] - app.origMousePos[1]; app.cameraEulers[0] = app.origCameraEulers[0] - static_cast(dy) * 0.25f; app.cameraEulers[1] = app.origCameraEulers[1] + static_cast(dx) * 0.25f; if (dx * dx + dy * dy > 3 * 3) { app.movedDuringRotate = true; } } break; case SDL_WINDOWEVENT: { if (event.window.event == SDL_WINDOWEVENT_RESIZED) { // Get the new window size app.width = event.window.data1; app.height = event.window.data2; // Update OpenGL viewport glViewport(0, 0, app.width, app.height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); constexpr float FOV = 50.0f; const float aspect = (float)app.width / (float)app.height; constexpr float zNear = 1.0f; const float zFar = app.camr; gluPerspective(FOV, aspect, zNear, zFar); } } break; case SDL_QUIT: done = true; break; default: break; } } Uint32 time = SDL_GetTicks(); float dt = static_cast(time - app.prevFrameTime) / 1000.0f; app.prevFrameTime = time; // Hit test mesh. if (processHitTest && app.inputGeometry && app.sample) { float hitTime; bool hit = app.inputGeometry->raycastMesh(app.rayStart, app.rayEnd, hitTime); if (hit) { if (SDL_GetModState() & KMOD_CTRL) { // Marker app.markerPositionSet = true; app.markerPosition[0] = app.rayStart[0] + (app.rayEnd[0] - app.rayStart[0]) * hitTime; app.markerPosition[1] = app.rayStart[1] + (app.rayEnd[1] - app.rayStart[1]) * hitTime; app.markerPosition[2] = app.rayStart[2] + (app.rayEnd[2] - app.rayStart[2]) * hitTime; } else { float hitPos[3]; hitPos[0] = app.rayStart[0] + (app.rayEnd[0] - app.rayStart[0]) * hitTime; hitPos[1] = app.rayStart[1] + (app.rayEnd[1] - app.rayStart[1]) * hitTime; hitPos[2] = app.rayStart[2] + (app.rayEnd[2] - app.rayStart[2]) * hitTime; app.sample->handleClick(app.rayStart, hitPos, processHitTestShift); } } else { if (SDL_GetModState() & KMOD_CTRL) { // Marker app.markerPositionSet = false; } } } // Update sample simulation. const float SIM_RATE = 20; const float DELTA_TIME = 1.0f / SIM_RATE; app.timeAcc = rcClamp(app.timeAcc + dt, -1.0f, 1.0f); int simIter = 0; while (app.timeAcc > DELTA_TIME) { app.timeAcc -= DELTA_TIME; if (simIter < 5 && app.sample) { app.sample->handleUpdate(DELTA_TIME); } simIter++; } // Clamp the framerate so that we do not hog all the CPU. const float MIN_FRAME_TIME = 1.0f / 40.0f; if (dt < MIN_FRAME_TIME) { int ms = (int)((MIN_FRAME_TIME - dt) * 1000.0f); if (ms > 10) { ms = 10; } if (ms >= 0) { SDL_Delay(ms); } } // Set the viewport. glViewport(0, 0, app.width, app.height); GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); // Clear the screen glClearColor(0.3f, 0.3f, 0.32f, 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glDisable(GL_TEXTURE_2D); glEnable(GL_DEPTH_TEST); // Compute the projection matrix. glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluPerspective(50.0f, (float)app.width / (float)app.height, 1.0f, app.camr); GLdouble projectionMatrix[16]; glGetDoublev(GL_PROJECTION_MATRIX, projectionMatrix); // Compute the modelview matrix. glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glRotatef(app.cameraEulers[0], 1, 0, 0); glRotatef(app.cameraEulers[1], 0, 1, 0); glTranslatef(-app.cameraPos[0], -app.cameraPos[1], -app.cameraPos[2]); GLdouble modelviewMatrix[16]; glGetDoublev(GL_MODELVIEW_MATRIX, modelviewMatrix); // Get hit ray position and direction. GLdouble x, y, z; gluUnProject(app.mousePos[0], app.mousePos[1], 0.0f, modelviewMatrix, projectionMatrix, viewport, &x, &y, &z); app.rayStart[0] = (float)x; app.rayStart[1] = (float)y; app.rayStart[2] = (float)z; gluUnProject(app.mousePos[0], app.mousePos[1], 1.0f, modelviewMatrix, projectionMatrix, viewport, &x, &y, &z); app.rayEnd[0] = (float)x; app.rayEnd[1] = (float)y; app.rayEnd[2] = (float)z; // Handle keyboard movement. const Uint8* keystate = SDL_GetKeyboardState(NULL); app.moveFront = rcClamp( app.moveFront + dt * 4 * ((keystate[SDL_SCANCODE_W] || keystate[SDL_SCANCODE_UP]) ? 1.0f : -1.0f), 0.0f, 1.0f); app.moveLeft = rcClamp( app.moveLeft + dt * 4 * ((keystate[SDL_SCANCODE_A] || keystate[SDL_SCANCODE_LEFT]) ? 1.0f : -1.0f), 0.0f, 1.0f); app.moveBack = rcClamp( app.moveBack + dt * 4 * ((keystate[SDL_SCANCODE_S] || keystate[SDL_SCANCODE_DOWN]) ? 1.0f : -1.0f), 0.0f, 1.0f); app.moveRight = rcClamp( app.moveRight + dt * 4 * ((keystate[SDL_SCANCODE_D] || keystate[SDL_SCANCODE_RIGHT]) ? 1.0f : -1.0f), 0.0f, 1.0f); app.moveUp = rcClamp( app.moveUp + dt * 4 * ((keystate[SDL_SCANCODE_Q] || keystate[SDL_SCANCODE_PAGEUP]) ? 1.0f : -1.0f), 0.0f, 1.0f); app.moveDown = rcClamp( app.moveDown + dt * 4 * ((keystate[SDL_SCANCODE_E] || keystate[SDL_SCANCODE_PAGEDOWN]) ? 1.0f : -1.0f), 0.0f, 1.0f); float keybSpeed = 22.0f; if (SDL_GetModState() & KMOD_SHIFT) { keybSpeed *= 4.0f; } float movex = (app.moveRight - app.moveLeft) * keybSpeed * dt; float movey = (app.moveBack - app.moveFront) * keybSpeed * dt + app.scrollZoom * 2.0f; app.scrollZoom = 0; app.cameraPos[0] += movex * static_cast(modelviewMatrix[0]); app.cameraPos[1] += movex * static_cast(modelviewMatrix[4]); app.cameraPos[2] += movex * static_cast(modelviewMatrix[8]); app.cameraPos[0] += movey * static_cast(modelviewMatrix[2]); app.cameraPos[1] += movey * static_cast(modelviewMatrix[6]); app.cameraPos[2] += movey * static_cast(modelviewMatrix[10]); app.cameraPos[1] += (app.moveUp - app.moveDown) * keybSpeed * dt; glEnable(GL_FOG); if (app.sample) { app.sample->handleRender(); } if (app.testCase) { app.testCase->handleRender(); } glDisable(GL_FOG); // Render GUI glDisable(GL_DEPTH_TEST); glMatrixMode(GL_PROJECTION); glLoadIdentity(); gluOrtho2D(0, app.width, 0, app.height); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); app.mouseOverMenu = false; ImGui_ImplOpenGL2_NewFrame(); ImGui_ImplSDL2_NewFrame(); ImGui::NewFrame(); ImGui::ShowDemoWindow(); if (app.sample) { app.sample->handleRenderOverlay((double*)projectionMatrix, (double*)modelviewMatrix, (int*)viewport); } if (app.testCase) { if (app.testCase->handleRenderOverlay((double*)projectionMatrix, (double*)modelviewMatrix, (int*)viewport)) { app.mouseOverMenu = true; } } // Help text. if (app.showMenu) { ImDrawList* draw_list = ImGui::GetForegroundDrawList(); draw_list->AddText({280.0f, 20.0f}, IM_COL32(255, 255, 255, 128), "W/A/S/D: Move RMB: Rotate"); } bool newMeshSelected = false; bool newSampleSelected = false; if (app.showMenu) { ImGui::SetNextWindowPos(ImVec2(app.width - 250 - 10, 10), ImGuiCond_Always); // Position in screen space ImGui::SetNextWindowSize(ImVec2(250, app.height - 20), ImGuiCond_Always); // Size of the window ImGui::Begin("Properties", nullptr, staticWindowFlags); ImGui::Checkbox("Show Log", &app.showLog); ImGui::Checkbox("Show Tools", &app.showTools); ImGui::SeparatorText("Input Mesh"); // Level selection dialog. if (ImGui::BeginCombo("##levelCombo", app.meshName.c_str(), 0)) { app.files.clear(); scanDirectory(app.meshesFolder, ".obj", app.files); scanDirectory(app.meshesFolder, ".gset", app.files); for (const auto& file : app.files) { const bool is_selected = (app.meshName == file); if (ImGui::Selectable(file.c_str(), is_selected) && !is_selected) { app.meshName = file; newMeshSelected = true; } // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } if (app.inputGeometry) { ImGui::Text( "Verts: %.1fk Tris: %.1fk", static_cast(app.inputGeometry->getVertCount()) / 1000.0f, static_cast(app.inputGeometry->getTriCount()) / 1000.0f); } ImGui::SeparatorText("Sample"); if (ImGui::BeginCombo("##sampleCombo", app.sampleIndex >= 0 ? g_samples[app.sampleIndex].name.c_str() : "Choose Sample...", 0)) { for (int n = 0; n < IM_ARRAYSIZE(g_samples); n++) { const bool is_selected = (app.sampleIndex == n); if (ImGui::Selectable(g_samples[n].name.c_str(), is_selected)) { newSampleSelected = !is_selected; app.sampleIndex = n; } // Set the initial focus when opening the combo (scrolling + keyboard navigation focus) if (is_selected) { ImGui::SetItemDefaultFocus(); } } ImGui::EndCombo(); } if (app.sample) { if (app.inputGeometry) { app.sample->handleSettings(); if (ImGui::Button("Build")) { app.buildContext.resetLog(); if (!app.sample->handleBuild()) { app.showLog = true; app.logScroll = 0; } app.buildContext.dumpLog("Build log %s:", app.meshName.c_str()); // Clear test. delete app.testCase; app.testCase = 0; } } ImGui::SeparatorText("Debug Settings"); app.sample->handleDebugMode(); } ImGui::End(); } if (newSampleSelected) { delete app.sample; app.sample = g_samples[app.sampleIndex].create(); app.sample->setContext(&app.buildContext); if (app.inputGeometry) { app.sample->handleMeshChanged(app.inputGeometry); app.resetCamera(); } } if (newMeshSelected) { string path = app.meshesFolder + "/" + app.meshName; delete app.inputGeometry; app.inputGeometry = new InputGeom; if (!app.inputGeometry->load(&app.buildContext, path)) { delete app.inputGeometry; app.inputGeometry = nullptr; // Destroy the sample if it already had geometry loaded, as we've just deleted it! if (app.sample && app.sample->getInputGeom()) { delete app.sample; app.sample = nullptr; } app.showLog = true; app.logScroll = 0; app.buildContext.dumpLog("geom load log %s:", app.meshName.c_str()); } app.resetCamera(); if (app.sample) { app.sample->handleMeshChanged(app.inputGeometry); } } #if 0 // Test cases if (app.showTestCases) { static int testScroll = 0; if (imguiBeginScrollArea( "Choose Test To Run", app.width - 10 - 250 - 10 - 200, app.height - 10 - 450, 200, 450, &testScroll)) { app.mouseOverMenu = true; } vector::const_iterator fileIter = app.files.begin(); vector::const_iterator filesEnd = app.files.end(); vector::const_iterator testToLoad = filesEnd; for (; fileIter != filesEnd; ++fileIter) { if (imguiItem(fileIter->c_str())) { testToLoad = fileIter; } } if (testToLoad != filesEnd) { string path = app.testCasesFolder + "/" + *testToLoad; app.testCase = new TestCase; if (app.testCase) { // Load the test. if (!app.testCase->load(path)) { delete app.testCase; app.testCase = 0; } // Create sample Sample* newSample = 0; for (int i = 0; i < g_nsamples; ++i) { if (g_samples[i].name == app.testCase->getSampleName()) { newSample = g_samples[i].create(); if (newSample) { app.sampleIndex = i; } } } delete app.sample; app.sample = newSample; if (app.sample) { app.sample->setContext(&app.buildContext); app.showSample = false; } // Load geom. app.meshName = app.testCase->getGeomFileName(); path = app.meshesFolder + "/" + app.meshName; delete app.inputGeometry; app.inputGeometry = new InputGeom; if (!app.inputGeometry->load(&app.buildContext, path)) { delete app.inputGeometry; app.inputGeometry = nullptr; delete app.sample; app.sample = nullptr; app.showLog = true; app.logScroll = 0; app.buildContext.dumpLog("geom load log %s:", app.meshName.c_str()); } if (app.sample && app.inputGeometry) { app.sample->handleMeshChanged(app.inputGeometry); } // This will ensure that tile & poly bits are updated in tiled sample. if (app.sample) { app.sample->handleSettings(); } app.buildContext.resetLog(); if (app.sample && !app.sample->handleBuild()) { app.buildContext.dumpLog("Build log %s:", app.meshName.c_str()); } if (app.inputGeometry || app.sample) { const float* bmin = 0; const float* bmax = 0; if (app.inputGeometry) { bmin = app.inputGeometry->getNavMeshBoundsMin(); bmax = app.inputGeometry->getNavMeshBoundsMax(); } // Reset camera and fog to match the mesh bounds. if (bmin && bmax) { app.camr = sqrtf(rcSqr(bmax[0] - bmin[0]) + rcSqr(bmax[1] - bmin[1]) + rcSqr(bmax[2] - bmin[2])) / 2; app.cameraPos[0] = (bmax[0] + bmin[0]) / 2 + app.camr; app.cameraPos[1] = (bmax[1] + bmin[1]) / 2 + app.camr; app.cameraPos[2] = (bmax[2] + bmin[2]) / 2 + app.camr; app.camr *= 3; } app.cameraEulers[0] = 45; app.cameraEulers[1] = -45; glFogf(GL_FOG_START, app.camr * 0.2f); glFogf(GL_FOG_END, app.camr * 1.25f); } // Do the tests. if (app.sample) { app.testCase->doTests(app.sample->getNavMesh(), app.sample->getNavMeshQuery()); } } } imguiEndScrollArea(); } #endif // Log static bool auto_scroll = true; if (app.showLog && app.showMenu) { ImGui::SetNextWindowPos(ImVec2(250 + 20, app.height - 200 - 10), ImGuiCond_Always); // Position in screen space ImGui::SetNextWindowSize(ImVec2(app.width - 250 - 250 - 20 - 20, 200), ImGuiCond_Always); // Size of the window ImGui::Begin("Log", nullptr, staticWindowFlags); for (int i = 0; i < app.buildContext.getLogCount(); ++i) { ImGui::TextUnformatted(app.buildContext.getLogText(i)); } ImGui::End(); } // Left column tools menu if (!app.showTestCases && app.showTools && app.showMenu) { ImGui::SetNextWindowPos(ImVec2(10, 10), ImGuiCond_Always); // Position in screen space ImGui::SetNextWindowSize(ImVec2(250, app.height - 20), ImGuiCond_Always); // Size of the window ImGui::Begin("Tools", nullptr, staticWindowFlags); if (app.sample) { app.sample->handleTools(); } ImGui::End(); } // Marker if (app.markerPositionSet && gluProject((GLdouble)app.markerPosition[0], (GLdouble)app.markerPosition[1], (GLdouble)app.markerPosition[2], modelviewMatrix, projectionMatrix, viewport, &x, &y, &z)) { // Draw marker circle glLineWidth(5.0f); glColor4ub(240, 220, 0, 196); glBegin(GL_LINE_LOOP); const float r = 25.0f; for (int i = 0; i < 20; ++i) { const float a = (float)i / 20.0f * RC_PI * 2; const float fx = (float)x + cosf(a) * r; const float fy = (float)y + sinf(a) * r; glVertex2f(fx, fy); } glEnd(); glLineWidth(1.0f); } glEnable(GL_DEPTH_TEST); ImGui::Render(); ImGui_ImplOpenGL2_RenderDrawData(ImGui::GetDrawData()); SDL_GL_SwapWindow(app.window); } ImGui_ImplOpenGL2_Shutdown(); ImGui_ImplSDL2_Shutdown(); ImGui::DestroyContext(); SDL_GL_DeleteContext(app.glContext); SDL_DestroyWindow(app.window); SDL_Quit(); delete app.sample; delete app.inputGeometry; return 0; }