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

Render debug view UI with ImGui

This commit is contained in:
Daniel Evans 2019-05-21 20:04:55 +01:00
parent cf38b449c5
commit 4202297e0e
7 changed files with 168 additions and 175 deletions

View File

@ -19,7 +19,7 @@ public:
, rotation(rot) { , rotation(rot) {
} }
glm::mat4 getView() { glm::mat4 getView() const {
auto up = rotation * glm::vec3(0.f, 0.f, 1.f); auto up = rotation * glm::vec3(0.f, 0.f, 1.f);
return glm::lookAt(position, return glm::lookAt(position,
position + rotation * glm::vec3(1.f, 0.f, 0.f), up); position + rotation * glm::vec3(1.f, 0.f, 0.f), up);

View File

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

View File

@ -27,7 +27,7 @@ public:
: near(near), far(far), fov(fov), aspectRatio(aspect) { : near(near), far(far), fov(fov), aspectRatio(aspect) {
} }
glm::mat4 projection(); glm::mat4 projection() const;
void update(const glm::mat4& proj); void update(const glm::mat4& proj);

View File

@ -483,7 +483,7 @@ bool RWGame::updateInput() {
RW_PROFILE_SCOPE(__func__); RW_PROFILE_SCOPE(__func__);
SDL_Event event; SDL_Event event;
while (SDL_PollEvent(&event)) { while (SDL_PollEvent(&event)) {
if (imgui.process_event(event)) { if (imgui.processEvent(event)) {
continue; continue;
} }
switch (event.type) { switch (event.type) {
@ -634,10 +634,12 @@ void RWGame::tickObjects(float dt) const {
void RWGame::render(float alpha, float time) { void RWGame::render(float alpha, float time) {
RW_PROFILE_SCOPEC(__func__, MP_CORNFLOWERBLUE); RW_PROFILE_SCOPEC(__func__, MP_CORNFLOWERBLUE);
RW_UNUSED(time);
lastDraws = getRenderer().getRenderer().getDrawCount(); lastDraws = getRenderer().getRenderer().getDrawCount();
getRenderer().getRenderer().swap(); getRenderer().getRenderer().swap();
imgui.startFrame();
// Update the camera // Update the camera
if (!stateManager.states.empty()) { if (!stateManager.states.empty()) {
@ -667,7 +669,7 @@ void RWGame::render(float alpha, float time) {
renderer.getRenderer().popDebugGroup(); renderer.getRenderer().popDebugGroup();
renderDebugView(time, viewCam); renderDebugView();
if (!world->isPaused()) hudDrawer.drawOnScreenText(world.get(), renderer); if (!world->isPaused()) hudDrawer.drawOnScreenText(world.get(), renderer);
@ -676,99 +678,25 @@ void RWGame::render(float alpha, float time) {
stateManager.draw(renderer); stateManager.draw(renderer);
} }
imgui.tick(); imgui.endFrame(viewCam);
} }
void RWGame::renderDebugView(float time, ViewCamera &viewCam) { void RWGame::renderDebugView() {
RW_PROFILE_SCOPE(__func__); RW_PROFILE_SCOPE(__func__);
switch (debugview_) { switch (debugview_) {
case DebugViewMode::General:
renderDebugStats(time);
break;
case DebugViewMode::Physics: case DebugViewMode::Physics:
world->dynamicsWorld->debugDrawWorld(); world->dynamicsWorld->debugDrawWorld();
debug.flush(renderer); debug.flush(renderer);
break; break;
case DebugViewMode::Navigation: case DebugViewMode::Navigation:
renderDebugPaths(time); renderDebugPaths();
break;
case DebugViewMode::Objects:
renderDebugObjects(time, viewCam);
break; break;
default: default:
break; break;
} }
} }
void RWGame::renderDebugStats(float time) { void RWGame::renderDebugPaths() {
// 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);
btVector3 roadColour(1.f, 0.f, 0.f); btVector3 roadColour(1.f, 0.f, 0.f);
btVector3 pedColour(0.f, 0.f, 1.f); btVector3 pedColour(0.f, 0.f, 1.f);
@ -872,74 +800,6 @@ void RWGame::renderDebugPaths(float time) {
debug.flush(renderer); 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) { void RWGame::globalKeyEvent(const SDL_Event& event) {
const auto toggle_debug = [&](DebugViewMode m) { const auto toggle_debug = [&](DebugViewMode m) {
debugview_ = debugview_ == m ? DebugViewMode::Disabled : m; debugview_ = debugview_ == m ? DebugViewMode::Disabled : m;

View File

@ -22,6 +22,16 @@
#include <chrono> #include <chrono>
class RWGame final : public GameBase { class RWGame final : public GameBase {
public:
enum class DebugViewMode {
Disabled,
General,
Physics,
Navigation,
Objects
};
private:
GameData data; GameData data;
GameRenderer renderer; GameRenderer renderer;
RWImGui imgui; RWImGui imgui;
@ -40,14 +50,6 @@ class RWGame final : public GameBase {
bool inFocus = true; bool inFocus = true;
ViewCamera currentCam; ViewCamera currentCam;
enum class DebugViewMode {
Disabled,
General,
Physics,
Navigation,
Objects
};
DebugViewMode debugview_ = DebugViewMode::Disabled; DebugViewMode debugview_ = DebugViewMode::Disabled;
int lastDraws{0}; /// Number of draws issued for the last frame. int lastDraws{0}; /// Number of draws issued for the last frame.
@ -92,6 +94,10 @@ public:
return hudDrawer; return hudDrawer;
} }
DebugViewMode getDebugViewMode() const {
return debugview_;
}
bool hitWorldRay(glm::vec3& hit, glm::vec3& normal, bool hitWorldRay(glm::vec3& hit, glm::vec3& normal,
GameObject** object = nullptr); GameObject** object = nullptr);
@ -112,9 +118,7 @@ private:
void tick(float dt); void tick(float dt);
void render(float alpha, float dt); void render(float alpha, float dt);
void renderDebugStats(float time); void renderDebugPaths();
void renderDebugPaths(float time);
void renderDebugObjects(float time, ViewCamera& camera);
void handleCheatInput(char symbol); void handleCheatInput(char symbol);
@ -124,7 +128,7 @@ private:
float tickWorld(const float deltaTime, float accumulatedTime); float tickWorld(const float deltaTime, float accumulatedTime);
void renderDebugView(float time, ViewCamera &viewCam); void renderDebugView();
void tickObjects(float dt) const; void tickObjects(float dt) const;
}; };

View File

@ -1,5 +1,9 @@
#include "RWImGui.hpp" #include "RWImGui.hpp"
#include <ai/CharacterController.hpp>
#include <objects/CharacterObject.hpp>
#include <objects/VehicleObject.hpp>
#ifdef RW_IMGUI #ifdef RW_IMGUI
#include "RWGame.hpp" #include "RWGame.hpp"
@ -8,7 +12,125 @@
#include <imgui_impl_sdl.h> #include <imgui_impl_sdl.h>
#include <imgui_impl_opengl3.h> #include <imgui_impl_opengl3.h>
#include <glm/glm.hpp>
#include <gl/gl_core_3_3.h> #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) RWImGui::RWImGui(RWGame &game)
: _game(game) { : _game(game) {
@ -39,7 +161,7 @@ void RWImGui::destroy() {
_context = nullptr; _context = nullptr;
} }
bool RWImGui::process_event(SDL_Event &event) { bool RWImGui::processEvent(SDL_Event& event) {
if (!_context) { if (!_context) {
return false; return false;
} }
@ -49,27 +171,32 @@ bool RWImGui::process_event(SDL_Event &event) {
return io.WantCaptureMouse || io.WantCaptureKeyboard; return io.WantCaptureMouse || io.WantCaptureKeyboard;
} }
void RWImGui::tick() { void RWImGui::startFrame() {
if (!_context) { if (!_context) {
return; return;
} }
ImGui::SetCurrentContext(_context); ImGui::SetCurrentContext(_context);
auto& io = ImGui::GetIO();
auto [window, sdl_glcontext] = _game.getWindow().getSDLContext(); auto [window, sdl_glcontext] = _game.getWindow().getSDLContext();
ImGui_ImplOpenGL3_NewFrame(); ImGui_ImplOpenGL3_NewFrame();
ImGui_ImplSDL2_NewFrame(window); ImGui_ImplSDL2_NewFrame(window);
ImGui::NewFrame(); ImGui::NewFrame();
}
static float f = 0.0f; 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;
}
ImGui::Begin("Hello, world!"); static bool show_demo_window = false;
ImGui::Text("Hello, world!");
ImGui::SliderFloat("float", &f, 0.0f, 1.0f);
ImGui::Text("Application average %.3f ms/frame (%.1f FPS)", static_cast<double>(1000.0f / io.Framerate), static_cast<double>(io.Framerate)); ImGui::End();
static bool show_demo_window = true;
if (show_demo_window) { if (show_demo_window) {
ImGui::ShowDemoWindow(&show_demo_window); ImGui::ShowDemoWindow(&show_demo_window);
} }

View File

@ -5,6 +5,7 @@
class RWGame; class RWGame;
struct ImGuiContext; struct ImGuiContext;
class ViewCamera;
class RWImGui { class RWImGui {
RWGame &_game; RWGame &_game;
@ -14,8 +15,9 @@ public:
~RWImGui(); ~RWImGui();
void init(); void init();
void destroy(); void destroy();
bool process_event(SDL_Event &event); bool processEvent(SDL_Event &event);
void tick(); void startFrame();
void endFrame(const ViewCamera &);
}; };
#endif // RWGAME_RWIMGUI_HPP #endif // RWGAME_RWIMGUI_HPP