diff --git a/.gitmodules b/.gitmodules index b044f920..cfc1cab8 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "external/microprofile/microprofile"] path = external/microprofile/microprofile url = https://github.com/jonasmr/microprofile.git +[submodule "imgui"] + path = external/imgui/imgui + url = https://github.com/ocornut/imgui.git diff --git a/external/CMakeLists.txt b/external/CMakeLists.txt index de3bbe45..8b03d62e 100644 --- a/external/CMakeLists.txt +++ b/external/CMakeLists.txt @@ -1,3 +1,5 @@ if(ENABLE_PROFILING) add_subdirectory(microprofile) endif() + +add_subdirectory(imgui) diff --git a/external/imgui/CMakeLists.txt b/external/imgui/CMakeLists.txt new file mode 100644 index 00000000..1befa8fd --- /dev/null +++ b/external/imgui/CMakeLists.txt @@ -0,0 +1,63 @@ +add_library(imgui EXCLUDE_FROM_ALL + rw_imconfig.h + imgui/imgui.h + imgui/imgui.cpp + imgui/imgui_demo.cpp + imgui/imgui_draw.cpp + imgui/imgui_internal.h + imgui/imgui_widgets.cpp + imgui/imstb_rectpack.h + imgui/imstb_textedit.h + imgui/imstb_truetype.h +) + +target_include_directories(imgui SYSTEM + PUBLIC + "${CMAKE_CURRENT_LIST_DIR}/imgui" +) + +target_compile_definitions(imgui + PUBLIC + IMGUI_USER_CONFIG="${CMAKE_CURRENT_SOURCE_DIR}/rw_imconfig.h" +) + +target_link_libraries(imgui + PUBLIC + openrw::checks +) + +add_library(imgui::core ALIAS imgui) + +openrw_target_apply_options( + TARGET imgui +) + +add_library(imgui_sdl_gl3 EXCLUDE_FROM_ALL + imgui/examples/imgui_impl_opengl3.h + imgui/examples/imgui_impl_opengl3.cpp + imgui/examples/imgui_impl_sdl.h + imgui/examples/imgui_impl_sdl.cpp +) + +target_include_directories(imgui_sdl_gl3 SYSTEM + PUBLIC + "${CMAKE_CURRENT_LIST_DIR}/imgui/examples" +) + +target_link_libraries(imgui_sdl_gl3 + PUBLIC + imgui::core + SDL2::SDL2 +) + +# FIXME: extract gl loader to target + add property to get header +target_compile_definitions(imgui_sdl_gl3 + PRIVATE + "IMGUI_IMPL_OPENGL_LOADER_CUSTOM=\"${OpenRW_SOURCE_DIR}/rwcore/gl/gl_core_3_3.h\"" +) + +add_library(imgui::sdl_gl3 ALIAS imgui_sdl_gl3) + +openrw_target_apply_options( + TARGET imgui_sdl_gl3 +) diff --git a/external/imgui/imgui b/external/imgui/imgui new file mode 160000 index 00000000..801645d3 --- /dev/null +++ b/external/imgui/imgui @@ -0,0 +1 @@ +Subproject commit 801645d35092c8da0eeabe71d7c1997c47aa3648 diff --git a/external/imgui/rw_imconfig.h b/external/imgui/rw_imconfig.h new file mode 100644 index 00000000..f6d6482d --- /dev/null +++ b/external/imgui/rw_imconfig.h @@ -0,0 +1,9 @@ +#ifndef RW_IMCONFIG_H +#define RW_IMCONFIG_H + +// Disable imgui assertions when not in debug mode +#ifndef RW_DEBUG +#define IM_ASSERT(MSG) //FIXME(madebr): remove comment +#endif + +#endif // RW_IMCONFIG_H diff --git a/rwengine/src/render/ViewCamera.hpp b/rwengine/src/render/ViewCamera.hpp index d2cd5927..b112f4ee 100644 --- a/rwengine/src/render/ViewCamera.hpp +++ b/rwengine/src/render/ViewCamera.hpp @@ -19,7 +19,7 @@ public: , rotation(rot) { } - glm::mat4 getView() { + glm::mat4 getView() const { auto up = rotation * glm::vec3(0.f, 0.f, 1.f); return glm::lookAt(position, position + rotation * glm::vec3(1.f, 0.f, 0.f), up); diff --git a/rwengine/src/render/ViewFrustum.cpp b/rwengine/src/render/ViewFrustum.cpp index 135fd2a1..ab6366b5 100644 --- a/rwengine/src/render/ViewFrustum.cpp +++ b/rwengine/src/render/ViewFrustum.cpp @@ -3,7 +3,7 @@ #include #include -glm::mat4 ViewFrustum::projection() { +glm::mat4 ViewFrustum::projection() const { return glm::perspective(fov / aspectRatio, aspectRatio, near, far); } diff --git a/rwengine/src/render/ViewFrustum.hpp b/rwengine/src/render/ViewFrustum.hpp index 927a0133..f00cd184 100644 --- a/rwengine/src/render/ViewFrustum.hpp +++ b/rwengine/src/render/ViewFrustum.hpp @@ -27,7 +27,7 @@ public: : near(near), far(far), fov(fov), aspectRatio(aspect) { } - glm::mat4 projection(); + glm::mat4 projection() const; void update(const glm::mat4& proj); diff --git a/rwgame/CMakeLists.txt b/rwgame/CMakeLists.txt index 2d555a8b..86e30200 100644 --- a/rwgame/CMakeLists.txt +++ b/rwgame/CMakeLists.txt @@ -15,6 +15,9 @@ add_library(librwgame STATIC GameWindow.hpp GameWindow.cpp + RWImGui.cpp + RWImGui.hpp + HUDDrawer.hpp HUDDrawer.cpp MenuSystem.hpp @@ -68,6 +71,16 @@ target_link_libraries(librwgame SDL2::SDL2 ) +target_compile_definitions(librwgame + PUBLIC + RW_IMGUI + ) + +target_link_libraries(librwgame + PUBLIC + imgui::sdl_gl3 + ) + add_executable(rwgame main.cpp ) diff --git a/rwgame/GameBase.cpp b/rwgame/GameBase.cpp index c827d32d..1d73f661 100644 --- a/rwgame/GameBase.cpp +++ b/rwgame/GameBase.cpp @@ -20,7 +20,7 @@ GameBase::GameBase(Logger &inlog, const std::optional &args) : bool fullscreen = config.fullscreen(); size_t w = config.width(), h = config.height(); - if (SDL_Init(SDL_INIT_VIDEO) < 0) + if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER) < 0) throw std::runtime_error("Failed to initialize SDL2!"); window.create(kWindowTitle + " [" + kBuildStr + "]", w, h, fullscreen); diff --git a/rwgame/GameWindow.hpp b/rwgame/GameWindow.hpp index 7d69d852..800a7ff0 100644 --- a/rwgame/GameWindow.hpp +++ b/rwgame/GameWindow.hpp @@ -5,8 +5,9 @@ #include #include - #include +#include +#include class GameWindow { SDL_Window* window = nullptr; @@ -30,6 +31,10 @@ public: bool isOpen() const { return !!window; } + + std::tuple getSDLContext() { + return std::make_tuple(window, glcontext); + } }; #endif diff --git a/rwgame/RWGame.cpp b/rwgame/RWGame.cpp index bbfed79f..e2e539d7 100644 --- a/rwgame/RWGame.cpp +++ b/rwgame/RWGame.cpp @@ -2,6 +2,7 @@ #include +#include "RWImGui.hpp" #include "GameInput.hpp" #include "State.hpp" #include "StateManager.hpp" @@ -28,7 +29,6 @@ #include #include #include -#include #ifdef _MSC_VER #pragma warning(disable : 4305 5033) @@ -54,7 +54,8 @@ constexpr float kMaxPhysicsSubSteps = 2; RWGame::RWGame(Logger& log, const std::optional &args) : GameBase(log, args) , data(&log, config.gamedataPath()) - , renderer(&log, &data) { + , renderer(&log, &data) + , imgui(*this) { RW_PROFILE_THREAD("Main"); RW_TIMELINE_ENTER("Startup", MP_YELLOW); @@ -76,6 +77,8 @@ RWGame::RWGame(Logger& log, const std::optional &args) config.gamedataPath()); } + imgui.init(); + data.load(); for (const auto& [specialModel, fileName, name] : kSpecialModels) { @@ -480,6 +483,9 @@ bool RWGame::updateInput() { RW_PROFILE_SCOPE(__func__); SDL_Event event; while (SDL_PollEvent(&event)) { + if (imgui.processEvent(event)) { + continue; + } switch (event.type) { case SDL_QUIT: return false; @@ -628,10 +634,12 @@ void RWGame::tickObjects(float dt) const { void RWGame::render(float alpha, float time) { RW_PROFILE_SCOPEC(__func__, MP_CORNFLOWERBLUE); + RW_UNUSED(time); lastDraws = getRenderer().getRenderer().getDrawCount(); getRenderer().getRenderer().swap(); + imgui.startFrame(); // Update the camera if (!stateManager.states.empty()) { @@ -661,7 +669,7 @@ void RWGame::render(float alpha, float time) { renderer.getRenderer().popDebugGroup(); - renderDebugView(time, viewCam); + renderDebugView(); if (!world->isPaused()) hudDrawer.drawOnScreenText(world.get(), renderer); @@ -669,98 +677,26 @@ void RWGame::render(float alpha, float time) { RW_PROFILE_SCOPE("state"); stateManager.draw(renderer); } + + imgui.endFrame(viewCam); } -void RWGame::renderDebugView(float time, ViewCamera &viewCam) { +void RWGame::renderDebugView() { RW_PROFILE_SCOPE(__func__); switch (debugview_) { - case DebugViewMode::General: - renderDebugStats(time); - break; case DebugViewMode::Physics: world->dynamicsWorld->debugDrawWorld(); debug.flush(renderer); break; case DebugViewMode::Navigation: - renderDebugPaths(time); - break; - case DebugViewMode::Objects: - renderDebugObjects(time, viewCam); + renderDebugPaths(); break; default: break; } } -void RWGame::renderDebugStats(float time) { - // Turn time into milliseconds - float time_ms = time * 1000.f; - constexpr size_t average_every_frame = 15; - static float times[average_every_frame]; - static size_t times_index = 0; - static float time_average = 0; - times[times_index++] = time_ms; - if (times_index >= average_every_frame) { - times_index = 0; - time_average = 0; - - for (size_t i = 0; i < average_every_frame; ++i) { - time_average += times[i]; - } - time_average /= average_every_frame; - } - - std::stringstream ss; - ss << "FPS: " << (1000.f / time_average) << " (" << time_average << "ms)\n" - << "Frame: " << time_ms << "ms\n" - << "Draws/Culls/Textures/Buffers: " << lastDraws << "/" - << renderer.getCulledCount() << "/" - << renderer.getRenderer().getTextureCount() << "/" - << renderer.getRenderer().getBufferCount() << "\n" - << "Timescale: " << world->state->basic.timeScale; - - TextRenderer::TextInfo ti; - ti.font = FONT_ARIAL; - ti.text = GameStringUtil::fromString(ss.str(), FONT_ARIAL); - ti.screenPosition = glm::vec2(10.f, 10.f); - ti.size = 15.f; - ti.baseColour = glm::u8vec3(255); - renderer.text.renderText(ti); - - /*while( engine->log.size() > 0 && engine->log.front().time + 10.f < - engine->gameTime ) { - engine->log.pop_front(); - } - - ti.screenPosition = glm::vec2( 10.f, 500.f ); - ti.size = 15.f; - for(auto it = engine->log.begin(); it != engine->log.end(); ++it) { - ti.text = it->message; - switch(it->type) { - case GameWorld::LogEntry::Error: - ti.baseColour = glm::vec3(1.f, 0.f, 0.f); - break; - case GameWorld::LogEntry::Warning: - ti.baseColour = glm::vec3(1.f, 1.f, 0.f); - break; - default: - ti.baseColour = glm::vec3(1.f, 1.f, 1.f); - break; - } - - // Interpolate the color - // c.a = (engine->gameTime - it->time > 5.f) ? 255 - (((engine->gameTime - - it->time) - 5.f)/5.f) * 255 : 255; - // text.setColor(c); - - engine->renderer.text.renderText(ti); - ti.screenPosition.y -= ti.size; - }*/ -} - -void RWGame::renderDebugPaths(float time) { - RW_UNUSED(time); - +void RWGame::renderDebugPaths() { btVector3 roadColour(1.f, 0.f, 0.f); btVector3 pedColour(0.f, 0.f, 1.f); @@ -864,74 +800,6 @@ void RWGame::renderDebugPaths(float time) { debug.flush(renderer); } -void RWGame::renderDebugObjects(float time, ViewCamera& camera) { - RW_UNUSED(time); - - std::stringstream ss; - - ss << "Models: " << data.modelinfo.size() << "\n" - << "Dynamic Objects:\n" - << " Vehicles: " << world->vehiclePool.objects.size() << "\n" - << " Peds: " << world->pedestrianPool.objects.size() << "\n"; - - TextRenderer::TextInfo ti; - ti.font = FONT_ARIAL; - ti.text = GameStringUtil::fromString(ss.str(), FONT_ARIAL); - ti.screenPosition = glm::vec2(10.f, 10.f); - ti.size = 15.f; - ti.baseColour = glm::u8vec3(255); - renderer.text.renderText(ti); - - // Render worldspace overlay for nearby objects - constexpr float kNearbyDistance = 25.f; - const auto& view = camera.position; - const auto& model = camera.getView(); - const auto& proj = camera.frustum.projection(); - const auto& size = getWindow().getSize(); - glm::vec4 viewport(0.f, 0.f, size.x, size.y); - auto isnearby = [&](GameObject* o) { - return glm::distance2(o->getPosition(), view) < - kNearbyDistance * kNearbyDistance; - }; - auto showdata = [&](GameObject* o, std::stringstream& ss) { - auto screen = glm::project(o->getPosition(), model, proj, viewport); - if (screen.z >= 1.f) { - return; - } - ti.text = GameStringUtil::fromString(ss.str(), FONT_ARIAL); - screen.y = viewport.w - screen.y; - ti.screenPosition = glm::vec2(screen); - ti.size = 10.f; - renderer.text.renderText(ti); - }; - - for (auto& p : world->vehiclePool.objects) { - if (!isnearby(p.second.get())) continue; - auto v = static_cast(p.second.get()); - - std::stringstream ss; - ss << v->getVehicle()->vehiclename_ << "\n" - << (v->isFlipped() ? "Flipped" : "Upright") << "\n" - << (v->isStopped() ? "Stopped" : "Moving") << "\n" - << v->getVelocity() << "m/s\n"; - - showdata(v, ss); - } - for (auto& p : world->pedestrianPool.objects) { - if (!isnearby(p.second.get())) continue; - auto c = static_cast(p.second.get()); - const auto& state = c->getCurrentState(); - auto act = c->controller->getCurrentActivity(); - - std::stringstream ss; - ss << "Health: " << state.health << " (" << state.armour << ")\n" - << (c->isAlive() ? "Alive" : "Dead") << "\n" - << "Activity: " << (act ? act->name() : "Idle") << "\n"; - - showdata(c, ss); - } -} - void RWGame::globalKeyEvent(const SDL_Event& event) { const auto toggle_debug = [&](DebugViewMode m) { debugview_ = debugview_ == m ? DebugViewMode::Disabled : m; diff --git a/rwgame/RWGame.hpp b/rwgame/RWGame.hpp index 965a02ff..ffd11aba 100644 --- a/rwgame/RWGame.hpp +++ b/rwgame/RWGame.hpp @@ -3,6 +3,8 @@ #include "GameBase.hpp" #include "HUDDrawer.hpp" +#include "RWConfig.hpp" +#include "RWImGui.hpp" #include "StateManager.hpp" #include "game.hpp" @@ -20,8 +22,19 @@ #include class RWGame final : public GameBase { +public: + enum class DebugViewMode { + Disabled, + General, + Physics, + Navigation, + Objects + }; + +private: GameData data; GameRenderer renderer; + RWImGui imgui; DebugDraw debug; GameState state; HUDDrawer hudDrawer{}; @@ -37,14 +50,6 @@ class RWGame final : public GameBase { bool inFocus = true; ViewCamera currentCam; - enum class DebugViewMode { - Disabled, - General, - Physics, - Navigation, - Objects - }; - DebugViewMode debugview_ = DebugViewMode::Disabled; int lastDraws{0}; /// Number of draws issued for the last frame. @@ -89,6 +94,10 @@ public: return hudDrawer; } + DebugViewMode getDebugViewMode() const { + return debugview_; + } + bool hitWorldRay(glm::vec3& hit, glm::vec3& normal, GameObject** object = nullptr); @@ -109,9 +118,7 @@ private: void tick(float dt); void render(float alpha, float dt); - void renderDebugStats(float time); - void renderDebugPaths(float time); - void renderDebugObjects(float time, ViewCamera& camera); + void renderDebugPaths(); void handleCheatInput(char symbol); @@ -121,7 +128,7 @@ private: float tickWorld(const float deltaTime, float accumulatedTime); - void renderDebugView(float time, ViewCamera &viewCam); + void renderDebugView(); void tickObjects(float dt) const; }; diff --git a/rwgame/RWImGui.cpp b/rwgame/RWImGui.cpp new file mode 100644 index 00000000..6ee496a5 --- /dev/null +++ b/rwgame/RWImGui.cpp @@ -0,0 +1,204 @@ +#include "RWImGui.hpp" + +#include +#include +#include + +#include "RWGame.hpp" + +#include +#include +#include + +#include + +#include +#include + +namespace { +void WindowDebugStats(RWGame& game) { + auto& io = ImGui::GetIO(); + + auto time_ms = 1000.0f / io.Framerate; + constexpr size_t average_every_frame = 240; + static float times[average_every_frame]; + static size_t times_index = 0; + static double time_average = 0, time_min = 0, time_max = 0; + times[times_index++] = time_ms; + if (times_index >= average_every_frame) { + times_index = 0; + time_average = 0; + time_min = std::numeric_limits::max(); + time_max = std::numeric_limits::lowest(); + + for (double time : times) { + time_average += time; + time_min = std::min(time, time_min); + time_max = std::max(time, time_max); + } + time_average /= average_every_frame; + } + + const auto& world = game.getWorld(); + auto& renderer = game.getRenderer(); + + ImGui::SetNextWindowPos({20.f, 20.f}); + ImGui::Begin("Engine Information", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoInputs); + ImGui::Text("%.3f ms/frame (%.1f FPS)\n%.3f / %.3f / %.3f ms", + static_cast(1000.0f / io.Framerate), + static_cast(io.Framerate), time_average, time_min, + time_max); + ImGui::Text("Timescale %.2f", + static_cast(world->state->basic.timeScale)); + ImGui::Text("%i Drawn %lu Culled", renderer.getRenderer().getDrawCount(), + renderer.getCulledCount()); + ImGui::Text("%i Textures %i Buffers", + renderer.getRenderer().getTextureCount(), + renderer.getRenderer().getBufferCount()); + ImGui::End(); +} + +void WindowDebugObjects(RWGame& game, const ViewCamera& camera) { + auto& data = game.getGameData(); + auto world = game.getWorld(); + + ImGui::SetNextWindowPos({20.f, 20.f}); + ImGui::Begin("Object Information", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoInputs); + ImGui::Text("%lu Models", data.modelinfo.size()); + ImGui::Text("Dynamic Objects\n %lu Vehicles\n %lu Peds", + world->vehiclePool.objects.size(), + world->pedestrianPool.objects.size()); + ImGui::End(); + + // Render worldspace overlay for nearby objects + constexpr float kNearbyDistance = 25.f; + const auto& view = camera.position; + const auto& model = camera.getView(); + const auto& proj = camera.frustum.projection(); + const auto& size = game.getWindow().getSize(); + glm::vec4 viewport(0.f, 0.f, size.x, size.y); + auto isnearby = [&](GameObject* o) { + return glm::distance2(o->getPosition(), view) < + kNearbyDistance * kNearbyDistance; + }; + auto showdata = [&](GameObject* o, std::stringstream& ss) { + auto screen = glm::project(o->getPosition(), model, proj, viewport); + if (screen.z >= 1.f) { + return; + } + ImGui::SetNextWindowPos({screen.x, viewport.w - screen.y}, 0, + {0.5f, 0.5f}); + ImGui::Begin( + std::to_string(reinterpret_cast(o)).c_str(), nullptr, + ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoInputs); + ImGui::Text("%s", ss.str().c_str()); + ImGui::End(); + }; + + for (auto& p : world->vehiclePool.objects) { + if (!isnearby(p.second.get())) continue; + auto v = static_cast(p.second.get()); + + std::stringstream ss; + ss << v->getVehicle()->vehiclename_ << "\n" + << (v->isFlipped() ? "Flipped" : "Upright") << "\n" + << (v->isStopped() ? "Stopped" : "Moving") << "\n" + << v->getVelocity() << "m/s\n"; + + showdata(v, ss); + } + for (auto& p : world->pedestrianPool.objects) { + if (!isnearby(p.second.get())) continue; + auto c = static_cast(p.second.get()); + const auto& state = c->getCurrentState(); + auto act = c->controller->getCurrentActivity(); + + std::stringstream ss; + ss << "Health: " << state.health << " (" << state.armour << ")\n" + << (c->isAlive() ? "Alive" : "Dead") << "\n" + << "Activity: " << (act ? act->name() : "Idle") << "\n"; + + showdata(c, ss); + } +} +} // namespace + +RWImGui::RWImGui(RWGame &game) + : _game(game) { +} + +RWImGui::~RWImGui() { + destroy(); +} + +void RWImGui::init() { + IMGUI_CHECKVERSION(); + _context = ImGui::CreateContext(); + + auto [window, context] = _game.getWindow().getSDLContext(); + + ImGui_ImplSDL2_InitForOpenGL(window, context); + ImGui_ImplOpenGL3_Init("#version 150"); +} + +void RWImGui::destroy() { + if (!_context) { + return; + } + ImGui::SetCurrentContext(_context); + ImGui_ImplOpenGL3_Shutdown(); + ImGui_ImplSDL2_Shutdown(); + ImGui::DestroyContext(); + _context = nullptr; +} + +bool RWImGui::processEvent(SDL_Event& event) { + if (!_context) { + return false; + } + ImGui::SetCurrentContext(_context); + ImGui_ImplSDL2_ProcessEvent(&event); + auto& io = ImGui::GetIO(); + return io.WantCaptureMouse || io.WantCaptureKeyboard; +} + +void RWImGui::startFrame() { + if (!_context) { + return; + } + ImGui::SetCurrentContext(_context); + + auto [window, sdl_glcontext] = _game.getWindow().getSDLContext(); + + ImGui_ImplOpenGL3_NewFrame(); + ImGui_ImplSDL2_NewFrame(window); + ImGui::NewFrame(); +} + +void RWImGui::endFrame(const ViewCamera& camera) { + switch (_game.getDebugViewMode()) { + case RWGame::DebugViewMode::General: + WindowDebugStats(_game); + break; + case RWGame::DebugViewMode::Objects: + WindowDebugObjects(_game, camera); + break; + default: + break; + } + + static bool show_demo_window = false; + if (show_demo_window) { + ImGui::ShowDemoWindow(&show_demo_window); + } + + ImGui::Render(); + ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData()); +} diff --git a/rwgame/RWImGui.hpp b/rwgame/RWImGui.hpp new file mode 100644 index 00000000..765d0a3a --- /dev/null +++ b/rwgame/RWImGui.hpp @@ -0,0 +1,23 @@ +#ifndef RWGAME_RWIMGUI_HPP +#define RWGAME_RWIMGUI_HPP + +#include + +class RWGame; +struct ImGuiContext; +class ViewCamera; + +class RWImGui { + RWGame &_game; + ImGuiContext *_context = nullptr; +public: + RWImGui(RWGame &game); + ~RWImGui(); + void init(); + void destroy(); + bool processEvent(SDL_Event &event); + void startFrame(); + void endFrame(const ViewCamera &); +}; + +#endif // RWGAME_RWIMGUI_HPP diff --git a/rwgame/states/DebugState.cpp b/rwgame/states/DebugState.cpp index f2460d41..fed4b2b3 100644 --- a/rwgame/states/DebugState.cpp +++ b/rwgame/states/DebugState.cpp @@ -17,10 +17,7 @@ #include #include -constexpr float kDebugEntryHeight = 14.f; -constexpr float kDebugEntryHeightMissions = 12.f; -constexpr int kDebugFont = 2; -const glm::vec2 kDebugMenuOffset = glm::vec2(10.f, 50.f); +#include static void jumpCharacter(RWGame* game, CharacterObject* player, const glm::vec3& target, bool ground = true) { @@ -38,126 +35,133 @@ static void jumpCharacter(RWGame* game, CharacterObject* player, } } -Menu DebugState::createDebugMenu() { +void DebugState::drawDebugMenu() { CharacterObject* player = nullptr; if (game->getWorld()->getPlayer()) { player = game->getWorld()->getPlayer()->getCharacter(); } - Menu menu{ - {{"Jump to Debug Camera", - [=] { - jumpCharacter(game, player, - _debugCam.position + - _debugCam.rotation * glm::vec3(3.f, 0.f, 0.f), - false); - }}, - {"-Map", [=] { setNextMenu(createMapMenu()); }}, - {"-Vehicles", [=] { setNextMenu(createVehicleMenu()); }}, - {"-AI", [=] { setNextMenu(createAIMenu()); }}, - {"-Weapons", [=] { setNextMenu(createWeaponMenu()); }}, - {"-Weather", [=] { setNextMenu(createWeatherMenu()); }}, - {"-Missions", [=] { setNextMenu(createMissionsMenu()); }}, - {"Set Super Jump", [=] { player->setJumpSpeed(20.f); }}, - {"Set Normal Jump", - [=] { player->setJumpSpeed(CharacterObject::DefaultJumpSpeed); }}, - {"Full Health", [=] { player->getCurrentState().health = 100.f; }}, - {"Full Armour", [=] { player->getCurrentState().armour = 100.f; }}, - {"Cull Here", - [=] { game->getRenderer().setCullOverride(true, _debugCam); }}}, - kDebugMenuOffset, - kDebugFont, - kDebugEntryHeight}; + ImGui::Begin("Debug Tools"); - // Optional block if the player is in a vehicle - auto cv = player->getCurrentVehicle(); - if (cv) { - menu.lambda("Flip vehicle", [=] { - cv->setRotation(cv->getRotation() * - glm::quat(glm::vec3(0.f, glm::pi(), 0.f))); - }); + if (player && ImGui::BeginMenu("Game")) { + if (ImGui::MenuItem("Set Super Jump")) { + player->setJumpSpeed(20.f); + } + if (ImGui::MenuItem("Set Normal Jump")) { + player->setJumpSpeed(CharacterObject::DefaultJumpSpeed); + } + + if (ImGui::MenuItem("Full Health")) { + player->getCurrentState().health = 100.f; + } + if (ImGui::MenuItem("Full Armour")) { + player->getCurrentState().armour = 100.f; + } + + // Optional block if the player is in a vehicle + if (auto cv = player->getCurrentVehicle(); cv) { + if (ImGui::MenuItem("Flip Vehicle")) { + cv->setRotation( + cv->getRotation() * + glm::quat(glm::vec3(0.f, glm::pi(), 0.f))); + } + } + + if (ImGui::MenuItem("Cull Here")) { + game->getRenderer().setCullOverride(true, _debugCam); + } + + ImGui::EndMenu(); } - return menu; + if (ImGui::BeginMenu("Map")) { + drawMapMenu(); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Vehicle")) { + drawVehicleMenu(); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("AI")) { + drawAIMenu(); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Weapons")) { + drawWeaponMenu(); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Weather")) { + drawWeatherMenu(); + ImGui::EndMenu(); + } + + if (ImGui::BeginMenu("Missions")) { + drawMissionsMenu(); + ImGui::EndMenu(); + } + + ImGui::End(); } -Menu DebugState::createMapMenu() { +void DebugState::drawMapMenu() { CharacterObject* player = nullptr; if (game->getWorld()->getPlayer()) { player = game->getWorld()->getPlayer()->getCharacter(); } - Menu menu{ - {{"Back", [=] { setNextMenu(createDebugMenu()); }}, - {"Jump to Docks", - [=] { - jumpCharacter(game, player, glm::vec3(1390.f, -837.f, 100.f)); - }}, - {"Jump to Garage", - [=] { jumpCharacter(game, player, glm::vec3(270.f, -605.f, 40.f)); }}, - {"Jump to Airport", - [=] { - jumpCharacter(game, player, glm::vec3(-950.f, -980.f, 12.f)); - }}, - {"Jump to Hideout", - [=] { - jumpCharacter(game, player, glm::vec3(875.0, -309.0, 100.0)); - }}, - {"Jump to Luigi's", - [=] { - jumpCharacter(game, player, glm::vec3(902.75, -425.56, 100.0)); - }}, - {"Jump to Hospital", - [=] { - jumpCharacter(game, player, glm::vec3(1123.77, -569.15, 100.0)); - }}, - {"Unsolid garage doors", - [=] { - static constexpr std::array garageDoorModels{ - {"8ballsuburbandoor", "amcogaragedoor", - "bankjobdoor", "bombdoor", - "crushercrush", "crushertop", - "door2_garage", "door3_garage", - "door4_garage", "door_bombshop", - "door_col_compnd_01", "door_col_compnd_02", - "door_col_compnd_03", "door_col_compnd_04", - "door_col_compnd_05", "door_jmsgrage", - "door_sfehousegrge", "double_garage_dr", - "impex_door", "impexpsubgrgdoor", - "ind_plyrwoor", "ind_slidedoor", - "jamesgrge_kb", "leveldoor2", - "oddjgaragdoor", "plysve_gragedoor", - "SalvGarage", "shedgaragedoor", - "Sub_sprayshopdoor", "towergaragedoor1", - "towergaragedoor2", "towergaragedoor3", - "vheistlocdoor"}}; + if (ImGui::MenuItem("Jump to Debug Camera")) { + jumpCharacter( + game, player, + _debugCam.position + _debugCam.rotation * glm::vec3(3.f, 0.f, 0.f), + false); + } - auto gw = game->getWorld(); - for (auto& [id, instancePtr] : gw->instancePool.objects) { - auto obj = static_cast(instancePtr.get()); - if (std::find(garageDoorModels.begin(), - garageDoorModels.end(), - obj->getModelInfo()->name) != - garageDoorModels.end()) { - obj->setSolid(false); - } - } - }}}, - kDebugMenuOffset, - kDebugFont, - kDebugEntryHeight}; - - return menu; -} - -Menu DebugState::createVehicleMenu() { - Menu menu{ - {{"Back", [=] { setNextMenu(createDebugMenu()); }}}, - kDebugMenuOffset, - kDebugFont, - kDebugEntryHeight, + const std::vector> kInterestingPlaces{ + {"Docks", {1390.f, -837.f, 100.f}}, + {"Garage", {270.f, -605.f, 40.f}}, + {"Airport", {-950.f, -980.f, 12.f}}, + {"Hideout", {875.0, -309.0, 100.0}}, + {"Luigi's", {902.75, -425.56, 100.0}}, + {"Hospital", {1123.77, -569.15, 100.0}}, }; + for (const auto& [name, pos] : kInterestingPlaces) { + if (ImGui::MenuItem((std::string("Jump to ") + name).c_str())) { + jumpCharacter(game, player, pos); + } + } + + if (ImGui::MenuItem("Unsolid Garage Doors")) { + static constexpr std::array garageDoorModels{ + {"8ballsuburbandoor", "amcogaragedoor", "bankjobdoor", + "bombdoor", "crushercrush", "crushertop", + "door2_garage", "door3_garage", "door4_garage", + "door_bombshop", "door_col_compnd_01", "door_col_compnd_02", + "door_col_compnd_03", "door_col_compnd_04", "door_col_compnd_05", + "door_jmsgrage", "door_sfehousegrge", "double_garage_dr", + "impex_door", "impexpsubgrgdoor", "ind_plyrwoor", + "ind_slidedoor", "jamesgrge_kb", "leveldoor2", + "oddjgaragdoor", "plysve_gragedoor", "SalvGarage", + "shedgaragedoor", "Sub_sprayshopdoor", "towergaragedoor1", + "towergaragedoor2", "towergaragedoor3", "vheistlocdoor"}}; + + auto gw = game->getWorld(); + for (auto& [id, instancePtr] : gw->instancePool.objects) { + auto obj = static_cast(instancePtr.get()); + if (std::find(garageDoorModels.begin(), garageDoorModels.end(), + obj->getModelInfo()->name) != + garageDoorModels.end()) { + obj->setSolid(false); + } + } + } +} + +void DebugState::drawVehicleMenu() { static constexpr std::array, 19> kVehicleTypes{{{"Landstalker", 90}, {"Taxi", 110}, @@ -180,18 +184,13 @@ Menu DebugState::createVehicleMenu() { {"Infernus", 101}}}; for (const auto& [name, id] : kVehicleTypes) { - menu.lambda(name, [this, id = id] { spawnVehicle(id); }); + if (ImGui::MenuItem(name)) { + spawnVehicle(id); + } } - - return menu; } -Menu DebugState::createAIMenu() { - Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}}, - kDebugMenuOffset, - kDebugFont, - kDebugEntryHeight}; - +void DebugState::drawAIMenu() { static constexpr std::array, 6> kPedTypes{{ {"Triad", 12}, @@ -203,10 +202,12 @@ Menu DebugState::createAIMenu() { }}; for (const auto& [name, id] : kPedTypes) { - menu.lambda(name, [this, id = id] { spawnFollower(id); }); + if (ImGui::MenuItem(name)) { + spawnFollower(id); + } } - menu.lambda("Kill All Peds", [=] { + if (ImGui::MenuItem("Kill All Peds")) { for (auto& [id, pedestrianPtr] : game->getWorld()->pedestrianPool.objects) { if (pedestrianPtr->getLifetime() == GameObject::PlayerLifetime) { @@ -220,47 +221,31 @@ Menu DebugState::createAIMenu() { 0.f }); } - }); - - return menu; + } } -Menu DebugState::createWeaponMenu() { - Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}}, - kDebugMenuOffset, - kDebugFont, - kDebugEntryHeight}; - +void DebugState::drawWeaponMenu() { for (int i = 1; i < kMaxInventorySlots; ++i) { auto& name = getWorld()->data->weaponData[i].name; - menu.lambda(name, [=] { giveItem(i); }); + if (ImGui::MenuItem(name.c_str())) { + giveItem(i); + } } - - return menu; } -Menu DebugState::createWeatherMenu() { - Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}}, - kDebugMenuOffset, - kDebugFont, - kDebugEntryHeight}; - - static constexpr std::array w{{"Sunny", "Cloudy", "Rainy", "Foggy"}}; +void DebugState::drawWeatherMenu() { + static constexpr std::array w{ + {"Sunny", "Cloudy", "Rainy", "Foggy"}}; for (std::size_t i = 0; i < w.size(); ++i) { - menu.lambda(w[i], - [=] { game->getWorld()->state->basic.nextWeather = static_cast(i); }); + if (ImGui::MenuItem(w[i])) { + game->getWorld()->state->basic.nextWeather = + static_cast(i); + } } - - return menu; } -Menu DebugState::createMissionsMenu() { - Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}}, - kDebugMenuOffset, - kDebugFont, - kDebugEntryHeightMissions}; - +void DebugState::drawMissionsMenu() { static constexpr std::array w{{ "Intro Movie", "Hospital Info Scene", @@ -345,7 +330,7 @@ Menu DebugState::createMissionsMenu() { }}; for (std::size_t i = 0; i < w.size(); ++i) { - menu.lambda(w[i], [=] { + if (ImGui::MenuItem(w[i])) { ScriptMachine* vm = game->getScriptVM(); if (vm) { @@ -367,16 +352,12 @@ Menu DebugState::createMissionsMenu() { vm->startThread(offsets[i], true); } - }); + } } - - return menu; } DebugState::DebugState(RWGame* game, const glm::vec3& vp, const glm::quat& vd) : State(game), _invertedY(game->getConfig().invertY()) { - this->setNextMenu(createDebugMenu()); - _debugCam.position = vp; _debugCam.rotation = vd; } @@ -400,19 +381,18 @@ void DebugState::tick(float dt) { } void DebugState::draw(GameRenderer& r) { - // Draw useful information like camera position. - std::stringstream ss; - ss << "Camera Position: " << glm::to_string(_debugCam.position) << "\n"; - auto zone = getWorld()->data->findZoneAt(_debugCam.position); - ss << (zone ? zone->name : "No Zone") << "\n"; + ImGui::SetNextWindowPos({20.f, 20.f}); + ImGui::Begin("Debug Info", nullptr, + ImGuiWindowFlags_NoDecoration | + ImGuiWindowFlags_NoSavedSettings | + ImGuiWindowFlags_NoInputs); - TextRenderer::TextInfo ti; - ti.font = FONT_ARIAL; - ti.text = GameStringUtil::fromString(ss.str(), ti.font); - ti.screenPosition = glm::vec2(10.f, 10.f); - ti.size = 15.f; - ti.baseColour = glm::u8vec3(255); - r.text.renderText(ti); + ImGui::Text("Camera: %s", glm::to_string(_debugCam.position).c_str()); + auto zone = getWorld()->data->findZoneAt(_debugCam.position); + ImGui::Text("Zone: %s", zone ? zone->name.c_str() : "No Zone"); + ImGui::End(); + + drawDebugMenu(); State::draw(r); } diff --git a/rwgame/states/DebugState.hpp b/rwgame/states/DebugState.hpp index 7b0839f1..8355def5 100644 --- a/rwgame/states/DebugState.hpp +++ b/rwgame/states/DebugState.hpp @@ -18,13 +18,13 @@ class DebugState final : public State { bool _sonicMode = false; bool _invertedY; - Menu createDebugMenu(); - Menu createMapMenu(); - Menu createVehicleMenu(); - Menu createAIMenu(); - Menu createWeaponMenu(); - Menu createWeatherMenu(); - Menu createMissionsMenu(); + void drawDebugMenu(); + void drawMapMenu(); + void drawVehicleMenu(); + void drawAIMenu(); + void drawWeaponMenu(); + void drawWeatherMenu(); + void drawMissionsMenu(); public: DebugState(RWGame* game, const glm::vec3& vp = {}, diff --git a/rwgame/states/LoadingState.cpp b/rwgame/states/LoadingState.cpp index ac87558f..2f8fc4a5 100644 --- a/rwgame/states/LoadingState.cpp +++ b/rwgame/states/LoadingState.cpp @@ -28,15 +28,13 @@ void LoadingState::handleEvent(const SDL_Event& e) { } void LoadingState::draw(GameRenderer& r) { - static auto kLoadingString = - GameStringUtil::fromString("Loading...", FONT_ARIAL); // Display some manner of loading screen. TextRenderer::TextInfo ti; - ti.text = kLoadingString; + ti.text = GameStringUtil::fromString("Loading...", FONT_ARIAL); auto size = r.getRenderer().getViewport(); ti.size = 25.f; ti.screenPosition = glm::vec2(50.f, size.y - ti.size - 50.f); - ti.font = FONT_PRICEDOWN; + ti.font = FONT_ARIAL; ti.baseColour = glm::u8vec3(255); r.text.renderText(ti); } diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 23438431..b2aae0a1 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -34,6 +34,7 @@ set(TESTS Text TrafficDirector Vehicle + ViewCamera VisualFX Weapon World diff --git a/tests/main.cpp b/tests/main.cpp index 07607815..a11ed160 100644 --- a/tests/main.cpp +++ b/tests/main.cpp @@ -3,6 +3,6 @@ #include "test_Globals.hpp" std::ostream& operator<<(std::ostream& stream, const glm::vec3& v) { - stream << v.x << " " << v.y << " " << v.z; + stream << glm::to_string(v); return stream; } diff --git a/tests/test_Globals.hpp b/tests/test_Globals.hpp index 760e0680..bba2f939 100644 --- a/tests/test_Globals.hpp +++ b/tests/test_Globals.hpp @@ -45,6 +45,12 @@ struct print_log_value { s << glm::to_string(v); } }; +template <> +struct print_log_value { + void operator()(std::ostream& s, glm::vec4 const& v) { + s << glm::to_string(v); + } +}; BOOST_NS_MAGIC_CLOSING } } diff --git a/tests/test_ViewCamera.cpp b/tests/test_ViewCamera.cpp new file mode 100644 index 00000000..056f411b --- /dev/null +++ b/tests/test_ViewCamera.cpp @@ -0,0 +1,26 @@ +#include +#include "test_Globals.hpp" +#include + +namespace { + +struct CameraFixture { + ViewCamera camera_ { + {1.f, 2.f, 3.f} + }; +}; +} + +BOOST_AUTO_TEST_SUITE(ViewCameraTests) + +BOOST_FIXTURE_TEST_CASE(test_creation, CameraFixture) { + BOOST_CHECK_EQUAL(camera_.position, glm::vec3(1.f, 2.f, 3.f)); +} + +BOOST_FIXTURE_TEST_CASE(test_view_matrix, CameraFixture) { + const auto& view = camera_.getView(); + BOOST_CHECK_EQUAL(view[3], glm::vec4(2.f, -3.f, 1.f, 1.f)); +} + + +BOOST_AUTO_TEST_SUITE_END()