1
0
mirror of https://github.com/rwengine/openrw.git synced 2024-11-25 11:52:40 +01:00

Merge pull request #717 from danhedron/imgui

Use ImGui for debugging
This commit is contained in:
Daniel Evans 2019-05-25 16:37:07 +01:00 committed by GitHub
commit c7d77084c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 550 additions and 341 deletions

3
.gitmodules vendored
View File

@ -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

View File

@ -1,3 +1,5 @@
if(ENABLE_PROFILING)
add_subdirectory(microprofile)
endif()
add_subdirectory(imgui)

63
external/imgui/CMakeLists.txt vendored Normal file
View File

@ -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
)

1
external/imgui/imgui vendored Submodule

@ -0,0 +1 @@
Subproject commit 801645d35092c8da0eeabe71d7c1997c47aa3648

9
external/imgui/rw_imconfig.h vendored Normal file
View File

@ -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

View File

@ -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);

View File

@ -3,7 +3,7 @@
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
glm::mat4 ViewFrustum::projection() {
glm::mat4 ViewFrustum::projection() const {
return glm::perspective(fov / aspectRatio, aspectRatio, near, far);
}

View File

@ -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);

View File

@ -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
)

View File

@ -20,7 +20,7 @@ GameBase::GameBase(Logger &inlog, const std::optional<RWArgConfigLayer> &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);

View File

@ -5,8 +5,9 @@
#include <SDL_video.h>
#include <glm/vec2.hpp>
#include <string>
#include <tuple>
#include <SDL.h>
class GameWindow {
SDL_Window* window = nullptr;
@ -30,6 +31,10 @@ public:
bool isOpen() const {
return !!window;
}
std::tuple<SDL_Window *, SDL_GLContext> getSDLContext() {
return std::make_tuple(window, glcontext);
}
};
#endif

View File

@ -2,6 +2,7 @@
#include <glm/gtx/norm.hpp>
#include "RWImGui.hpp"
#include "GameInput.hpp"
#include "State.hpp"
#include "StateManager.hpp"
@ -28,7 +29,6 @@
#include <functional>
#include <iomanip>
#include <iostream>
#include <algorithm>
#ifdef _MSC_VER
#pragma warning(disable : 4305 5033)
@ -54,7 +54,8 @@ constexpr float kMaxPhysicsSubSteps = 2;
RWGame::RWGame(Logger& log, const std::optional<RWArgConfigLayer> &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<RWArgConfigLayer> &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<VehicleObject*>(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<CharacterObject*>(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;

View File

@ -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 <chrono>
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;
};

204
rwgame/RWImGui.cpp Normal file
View File

@ -0,0 +1,204 @@
#include "RWImGui.hpp"
#include <ai/CharacterController.hpp>
#include <objects/CharacterObject.hpp>
#include <objects/VehicleObject.hpp>
#include "RWGame.hpp"
#include <imgui.h>
#include <imgui_impl_sdl.h>
#include <imgui_impl_opengl3.h>
#include <glm/glm.hpp>
#include <gl/gl_core_3_3.h>
#include <glm/gtx/norm.hpp>
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<double>::max();
time_max = std::numeric_limits<double>::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<double>(1000.0f / io.Framerate),
static_cast<double>(io.Framerate), time_average, time_min,
time_max);
ImGui::Text("Timescale %.2f",
static_cast<double>(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<uintptr_t>(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<VehicleObject*>(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<CharacterObject*>(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());
}

23
rwgame/RWImGui.hpp Normal file
View File

@ -0,0 +1,23 @@
#ifndef RWGAME_RWIMGUI_HPP
#define RWGAME_RWIMGUI_HPP
#include <SDL.h>
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

View File

@ -17,10 +17,7 @@
#include <iostream>
#include <sstream>
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 <imgui.h>
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");
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
auto cv = player->getCurrentVehicle();
if (cv) {
menu.lambda("Flip vehicle", [=] {
cv->setRotation(cv->getRotation() *
if (auto cv = player->getCurrentVehicle(); cv) {
if (ImGui::MenuItem("Flip Vehicle")) {
cv->setRotation(
cv->getRotation() *
glm::quat(glm::vec3(0.f, glm::pi<float>(), 0.f)));
});
}
}
return menu;
if (ImGui::MenuItem("Cull Here")) {
game->getRenderer().setCullOverride(true, _debugCam);
}
ImGui::EndMenu();
}
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",
[=] {
if (ImGui::MenuItem("Jump to Debug Camera")) {
jumpCharacter(
game, player,
_debugCam.position + _debugCam.rotation * glm::vec3(3.f, 0.f, 0.f),
false);
}
const std::vector<std::tuple<const char*, glm::vec3>> 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<char const*, 33> 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"}};
{"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<InstanceObject*>(instancePtr.get());
if (std::find(garageDoorModels.begin(),
garageDoorModels.end(),
if (std::find(garageDoorModels.begin(), garageDoorModels.end(),
obj->getModelInfo<BaseModelInfo>()->name) !=
garageDoorModels.end()) {
obj->setSolid(false);
}
}
}}},
kDebugMenuOffset,
kDebugFont,
kDebugEntryHeight};
return menu;
}
}
Menu DebugState::createVehicleMenu() {
Menu menu{
{{"Back", [=] { setNextMenu(createDebugMenu()); }}},
kDebugMenuOffset,
kDebugFont,
kDebugEntryHeight,
};
void DebugState::drawVehicleMenu() {
static constexpr std::array<std::tuple<char const*, unsigned int>, 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<std::tuple<char const*, unsigned int>, 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<char const*, 4> w{{"Sunny", "Cloudy", "Rainy", "Foggy"}};
void DebugState::drawWeatherMenu() {
static constexpr std::array<char const*, 4> 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<std::uint16_t>(i); });
if (ImGui::MenuItem(w[i])) {
game->getWorld()->state->basic.nextWeather =
static_cast<std::uint16_t>(i);
}
}
return menu;
}
Menu DebugState::createMissionsMenu() {
Menu menu{{{"Back", [=] { setNextMenu(createDebugMenu()); }}},
kDebugMenuOffset,
kDebugFont,
kDebugEntryHeightMissions};
void DebugState::drawMissionsMenu() {
static constexpr std::array<char const*, 80> 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);
}

View File

@ -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 = {},

View File

@ -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);
}

View File

@ -34,6 +34,7 @@ set(TESTS
Text
TrafficDirector
Vehicle
ViewCamera
VisualFX
Weapon
World

View File

@ -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;
}

View File

@ -45,6 +45,12 @@ struct print_log_value<glm::vec3> {
s << glm::to_string(v);
}
};
template <>
struct print_log_value<glm::vec4> {
void operator()(std::ostream& s, glm::vec4 const& v) {
s << glm::to_string(v);
}
};
BOOST_NS_MAGIC_CLOSING
}
}

26
tests/test_ViewCamera.cpp Normal file
View File

@ -0,0 +1,26 @@
#include <boost/test/unit_test.hpp>
#include "test_Globals.hpp"
#include <render/ViewCamera.hpp>
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()