Add debug mode that shows mesh buffer bounding boxes

This commit is contained in:
sfan5
2025-01-02 11:18:37 +01:00
parent 9554e3d43a
commit 4e2ca05f08
8 changed files with 70 additions and 46 deletions

@ -34,7 +34,7 @@ enum E_DEBUG_SCENE_TYPE
EDS_BBOX_ALL = EDS_BBOX | EDS_BBOX_BUFFERS, EDS_BBOX_ALL = EDS_BBOX | EDS_BBOX_BUFFERS,
//! Show all debug infos //! Show all debug infos
EDS_FULL = 0xffffffff EDS_FULL = 0xffff
}; };
} // end namespace scene } // end namespace scene

@ -387,6 +387,14 @@ public:
pass currently is active they can render the correct part of their geometry. */ pass currently is active they can render the correct part of their geometry. */
virtual E_SCENE_NODE_RENDER_PASS getSceneNodeRenderPass() const = 0; virtual E_SCENE_NODE_RENDER_PASS getSceneNodeRenderPass() const = 0;
/**
* Sets debug data flags that will be set on every rendered scene node.
* Refer to `E_DEBUG_SCENE_TYPE`.
* @param setBits bit mask of types to enable
* @param unsetBits bit mask of types to disable
*/
virtual void setGlobalDebugData(u16 setBits, u16 unsetBits) = 0;
//! Creates a new scene manager. //! Creates a new scene manager.
/** This can be used to easily draw and/or store two /** This can be used to easily draw and/or store two
independent scenes at the same time. The mesh cache will be independent scenes at the same time. The mesh cache will be

@ -403,14 +403,14 @@ public:
their geometry because it is their only reason for existence, their geometry because it is their only reason for existence,
for example the OctreeSceneNode. for example the OctreeSceneNode.
\param state The culling state to be used. Check E_CULLING_TYPE for possible values.*/ \param state The culling state to be used. Check E_CULLING_TYPE for possible values.*/
void setAutomaticCulling(u32 state) void setAutomaticCulling(u16 state)
{ {
AutomaticCullingState = state; AutomaticCullingState = state;
} }
//! Gets the automatic culling state. //! Gets the automatic culling state.
/** \return The automatic culling state. */ /** \return The automatic culling state. */
u32 getAutomaticCulling() const u16 getAutomaticCulling() const
{ {
return AutomaticCullingState; return AutomaticCullingState;
} }
@ -419,7 +419,7 @@ public:
/** A bitwise OR of the types from @ref irr::scene::E_DEBUG_SCENE_TYPE. /** A bitwise OR of the types from @ref irr::scene::E_DEBUG_SCENE_TYPE.
Please note that not all scene nodes support all debug data types. Please note that not all scene nodes support all debug data types.
\param state The debug data visibility state to be used. */ \param state The debug data visibility state to be used. */
virtual void setDebugDataVisible(u32 state) virtual void setDebugDataVisible(u16 state)
{ {
DebugDataVisible = state; DebugDataVisible = state;
} }
@ -427,7 +427,7 @@ public:
//! Returns if debug data like bounding boxes are drawn. //! Returns if debug data like bounding boxes are drawn.
/** \return A bitwise OR of the debug data values from /** \return A bitwise OR of the debug data values from
@ref irr::scene::E_DEBUG_SCENE_TYPE that are currently visible. */ @ref irr::scene::E_DEBUG_SCENE_TYPE that are currently visible. */
u32 isDebugDataVisible() const u16 isDebugDataVisible() const
{ {
return DebugDataVisible; return DebugDataVisible;
} }
@ -581,10 +581,10 @@ protected:
s32 ID; s32 ID;
//! Automatic culling state //! Automatic culling state
u32 AutomaticCullingState; u16 AutomaticCullingState;
//! Flag if debug data should be drawn, such as Bounding Boxes. //! Flag if debug data should be drawn, such as Bounding Boxes.
u32 DebugDataVisible; u16 DebugDataVisible;
//! Is the node visible? //! Is the node visible?
bool IsVisible; bool IsVisible;

@ -276,9 +276,6 @@ void CAnimatedMeshSceneNode::render()
debug_mat.ZBuffer = video::ECFN_DISABLED; debug_mat.ZBuffer = video::ECFN_DISABLED;
driver->setMaterial(debug_mat); driver->setMaterial(debug_mat);
if (DebugDataVisible & scene::EDS_BBOX)
driver->draw3DBox(Box, video::SColor(255, 255, 255, 255));
// show bounding box // show bounding box
if (DebugDataVisible & scene::EDS_BBOX_BUFFERS) { if (DebugDataVisible & scene::EDS_BBOX_BUFFERS) {
for (u32 g = 0; g < m->getMeshBufferCount(); ++g) { for (u32 g = 0; g < m->getMeshBufferCount(); ++g) {
@ -290,6 +287,9 @@ void CAnimatedMeshSceneNode::render()
} }
} }
if (DebugDataVisible & scene::EDS_BBOX)
driver->draw3DBox(Box, video::SColor(255, 255, 255, 255));
// show skeleton // show skeleton
if (DebugDataVisible & scene::EDS_SKELETON) { if (DebugDataVisible & scene::EDS_SKELETON) {
if (Mesh->getMeshType() == EAMT_SKINNED) { if (Mesh->getMeshType() == EAMT_SKINNED) {

@ -110,11 +110,9 @@ void CMeshSceneNode::render()
if (DebugDataVisible && PassCount == 1) { if (DebugDataVisible && PassCount == 1) {
video::SMaterial m; video::SMaterial m;
m.AntiAliasing = 0; m.AntiAliasing = 0;
m.ZBuffer = video::ECFN_DISABLED;
driver->setMaterial(m); driver->setMaterial(m);
if (DebugDataVisible & scene::EDS_BBOX) {
driver->draw3DBox(Box, video::SColor(255, 255, 255, 255));
}
if (DebugDataVisible & scene::EDS_BBOX_BUFFERS) { if (DebugDataVisible & scene::EDS_BBOX_BUFFERS) {
for (u32 g = 0; g < Mesh->getMeshBufferCount(); ++g) { for (u32 g = 0; g < Mesh->getMeshBufferCount(); ++g) {
driver->draw3DBox( driver->draw3DBox(
@ -123,6 +121,10 @@ void CMeshSceneNode::render()
} }
} }
if (DebugDataVisible & scene::EDS_BBOX) {
driver->draw3DBox(Box, video::SColor(255, 255, 255, 255));
}
if (DebugDataVisible & scene::EDS_NORMALS) { if (DebugDataVisible & scene::EDS_NORMALS) {
// draw normals // draw normals
const f32 debugNormalLength = 1.f; const f32 debugNormalLength = 1.f;

@ -490,13 +490,19 @@ void CSceneManager::drawAll()
// let all nodes register themselves // let all nodes register themselves
OnRegisterSceneNode(); OnRegisterSceneNode();
const auto &render_node = [this] (ISceneNode *node) {
u32 flags = node->isDebugDataVisible();
node->setDebugDataVisible((flags & DebugDataMask) | DebugDataBits);
node->render();
};
// render camera scenes // render camera scenes
{ {
CurrentRenderPass = ESNRP_CAMERA; CurrentRenderPass = ESNRP_CAMERA;
Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0);
for (auto *node : CameraList) for (auto *node : CameraList)
node->render(); render_node(node);
CameraList.clear(); CameraList.clear();
} }
@ -507,7 +513,7 @@ void CSceneManager::drawAll()
Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0);
for (auto *node : SkyBoxList) for (auto *node : SkyBoxList)
node->render(); render_node(node);
SkyBoxList.clear(); SkyBoxList.clear();
} }
@ -520,7 +526,7 @@ void CSceneManager::drawAll()
std::sort(SolidNodeList.begin(), SolidNodeList.end()); std::sort(SolidNodeList.begin(), SolidNodeList.end());
for (auto &it : SolidNodeList) for (auto &it : SolidNodeList)
it.Node->render(); render_node(it.Node);
SolidNodeList.clear(); SolidNodeList.clear();
} }
@ -533,7 +539,7 @@ void CSceneManager::drawAll()
std::sort(TransparentNodeList.begin(), TransparentNodeList.end()); std::sort(TransparentNodeList.begin(), TransparentNodeList.end());
for (auto &it : TransparentNodeList) for (auto &it : TransparentNodeList)
it.Node->render(); render_node(it.Node);
TransparentNodeList.clear(); TransparentNodeList.clear();
} }
@ -546,7 +552,7 @@ void CSceneManager::drawAll()
std::sort(TransparentEffectNodeList.begin(), TransparentEffectNodeList.end()); std::sort(TransparentEffectNodeList.begin(), TransparentEffectNodeList.end());
for (auto &it : TransparentEffectNodeList) for (auto &it : TransparentEffectNodeList)
it.Node->render(); render_node(it.Node);
TransparentEffectNodeList.clear(); TransparentEffectNodeList.clear();
} }
@ -557,7 +563,7 @@ void CSceneManager::drawAll()
Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0); Driver->getOverrideMaterial().Enabled = ((Driver->getOverrideMaterial().EnablePasses & CurrentRenderPass) != 0);
for (auto *node : GuiNodeList) for (auto *node : GuiNodeList)
node->render(); render_node(node);
GuiNodeList.clear(); GuiNodeList.clear();
} }

@ -179,6 +179,11 @@ public:
//! Set current render time. //! Set current render time.
void setCurrentRenderPass(E_SCENE_NODE_RENDER_PASS nextPass) override { CurrentRenderPass = nextPass; } void setCurrentRenderPass(E_SCENE_NODE_RENDER_PASS nextPass) override { CurrentRenderPass = nextPass; }
void setGlobalDebugData(u16 setBits, u16 unsetBits) override {
DebugDataMask = ~unsetBits;
DebugDataBits = setBits;
}
//! returns if node is culled //! returns if node is culled
bool isCulled(const ISceneNode *node) const override; bool isCulled(const ISceneNode *node) const override;
@ -268,6 +273,9 @@ private:
//! Mesh cache //! Mesh cache
IMeshCache *MeshCache; IMeshCache *MeshCache;
//! Global debug render state
u16 DebugDataMask = 0, DebugDataBits = 0;
E_SCENE_NODE_RENDER_PASS CurrentRenderPass; E_SCENE_NODE_RENDER_PASS CurrentRenderPass;
}; };

@ -427,6 +427,8 @@ public:
const static float object_hit_delay = 0.2; const static float object_hit_delay = 0.2;
const static u16 bbox_debug_flag = scene::EDS_BBOX_ALL;
/* The reason the following structs are not anonymous structs within the /* The reason the following structs are not anonymous structs within the
* class is that they are not used by the majority of member functions and * class is that they are not used by the majority of member functions and
* many functions that do require objects of thse types do not modify them * many functions that do require objects of thse types do not modify them
@ -635,6 +637,8 @@ protected:
private: private:
struct Flags { struct Flags {
bool disable_camera_update = false; bool disable_camera_update = false;
/// 0 = no debug text active, see toggleDebug() for the rest
int debug_state = 0;
}; };
void pauseAnimation(); void pauseAnimation();
@ -1663,6 +1667,7 @@ void Game::updateDebugState()
hud->disableBlockBounds(); hud->disableBlockBounds();
if (!has_debug) { if (!has_debug) {
draw_control->show_wireframe = false; draw_control->show_wireframe = false;
smgr->setGlobalDebugData(0, bbox_debug_flag);
m_flags.disable_camera_update = false; m_flags.disable_camera_update = false;
m_game_formspec.disableDebugView(); m_game_formspec.disableDebugView();
} }
@ -2222,46 +2227,41 @@ void Game::toggleDebug()
LocalPlayer *player = client->getEnv().getLocalPlayer(); LocalPlayer *player = client->getEnv().getLocalPlayer();
bool has_debug = client->checkPrivilege("debug"); bool has_debug = client->checkPrivilege("debug");
bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG); bool has_basic_debug = has_debug || (player->hud_flags & HUD_FLAG_BASIC_DEBUG);
// Initial: No debug info // Initial: No debug info
// 1x toggle: Debug text // 1x toggle: Debug text
// 2x toggle: Debug text with profiler graph // 2x toggle: Debug text with profiler graph
// 3x toggle: Debug text and wireframe (needs "debug" priv) // 3x toggle: Debug text and wireframe (needs "debug" priv)
// Next toggle: Back to initial // 4x toggle: Debug text and bbox (needs "debug" priv)
// //
// The debug text can be in 2 modes: minimal and basic. // The debug text can be in 2 modes: minimal and basic.
// * Minimal: Only technical client info that not gameplay-relevant // * Minimal: Only technical client info that not gameplay-relevant
// * Basic: Info that might give gameplay advantage, e.g. pos, angle // * Basic: Info that might give gameplay advantage, e.g. pos, angle
// Basic mode is used when player has the debug HUD flag set, // Basic mode is used when player has the debug HUD flag set,
// otherwise the Minimal mode is used. // otherwise the Minimal mode is used.
if (!m_game_ui->m_flags.show_minimal_debug) {
m_game_ui->m_flags.show_minimal_debug = true; auto &state = m_flags.debug_state;
if (has_basic_debug) state = (state + 1) % 5;
m_game_ui->m_flags.show_basic_debug = true; if (state >= 3 && !has_debug)
m_game_ui->m_flags.show_profiler_graph = false; state = 0;
draw_control->show_wireframe = false;
m_game_ui->m_flags.show_minimal_debug = state > 0;
m_game_ui->m_flags.show_basic_debug = state > 0 && has_basic_debug;
m_game_ui->m_flags.show_profiler_graph = state == 2;
draw_control->show_wireframe = state == 3;
smgr->setGlobalDebugData(state == 4 ? bbox_debug_flag : 0,
state == 4 ? 0 : bbox_debug_flag);
if (state == 1)
m_game_ui->showTranslatedStatusText("Debug info shown"); m_game_ui->showTranslatedStatusText("Debug info shown");
} else if (!m_game_ui->m_flags.show_profiler_graph && !draw_control->show_wireframe) { else if (state == 2)
if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
m_game_ui->m_flags.show_profiler_graph = true;
m_game_ui->showTranslatedStatusText("Profiler graph shown"); m_game_ui->showTranslatedStatusText("Profiler graph shown");
} else if (!draw_control->show_wireframe && client->checkPrivilege("debug")) { else if (state == 3)
if (has_basic_debug)
m_game_ui->m_flags.show_basic_debug = true;
m_game_ui->m_flags.show_profiler_graph = false;
draw_control->show_wireframe = true;
m_game_ui->showTranslatedStatusText("Wireframe shown"); m_game_ui->showTranslatedStatusText("Wireframe shown");
} else { else if (state == 4)
m_game_ui->m_flags.show_minimal_debug = false; m_game_ui->showTranslatedStatusText("Bounding boxes shown");
m_game_ui->m_flags.show_basic_debug = false; else
m_game_ui->m_flags.show_profiler_graph = false; m_game_ui->showTranslatedStatusText("All debug info hidden");
draw_control->show_wireframe = false;
if (has_debug) {
m_game_ui->showTranslatedStatusText("Debug info, profiler graph, and wireframe hidden");
} else {
m_game_ui->showTranslatedStatusText("Debug info and profiler graph hidden");
}
}
} }