From 157b16f9ef9d99a6f4a36db9ed4430be88b348d0 Mon Sep 17 00:00:00 2001 From: Michael Alexsander Date: Tue, 14 Apr 2026 17:14:09 -0300 Subject: [PATCH] Add octant visualization to `GridMap` --- modules/gridmap/doc_classes/GridMap.xml | 6 + modules/gridmap/grid_map.cpp | 222 +++++++++++++++++++++++- modules/gridmap/grid_map.h | 27 +++ 3 files changed, 246 insertions(+), 9 deletions(-) diff --git a/modules/gridmap/doc_classes/GridMap.xml b/modules/gridmap/doc_classes/GridMap.xml index 12417c0d8a..490f5de16e 100644 --- a/modules/gridmap/doc_classes/GridMap.xml +++ b/modules/gridmap/doc_classes/GridMap.xml @@ -268,6 +268,12 @@ Show or hide the [GridMap]'s collision shapes. If set to [constant DEBUG_VISIBILITY_MODE_DEFAULT], this depends on the show collision debug settings. + + The [Color] used for rendering octant debug visuals when [member debug_show_octants] is enabled. + + + If [code]true[/code], shows debug visuals for octants. + The assigned [MeshLibrary]. diff --git a/modules/gridmap/grid_map.cpp b/modules/gridmap/grid_map.cpp index 9b293ae72d..d6f5c921b0 100644 --- a/modules/gridmap/grid_map.cpp +++ b/modules/gridmap/grid_map.cpp @@ -362,6 +362,134 @@ bool GridMap::get_center_z() const { return center_z; } +void GridMap::set_debug_octant_color(const Color &p_color) { +#ifdef DEBUG_ENABLED + if (debug_octant_color == p_color) { + return; + } + + debug_octant_color = p_color; + + if (debug_octant_line_material.is_valid()) { + debug_octant_line_material->set_albedo(debug_octant_color); + } +#endif +} + +Color GridMap::get_debug_octant_color() const { + return debug_octant_color; +} + +#ifdef DEBUG_ENABLED +void GridMap::_debug_update() { + if (debug_dirty || !is_inside_tree()) { + return; + } + debug_dirty = true; + + callable_mp(this, &GridMap::_debug_update_octants).call_deferred(); +} + +void GridMap::_debug_update_octants() { + debug_dirty = false; + + if (!debug_show_octants) { + _debug_clear_octants(); + return; + } + + if (debug_octant_line_material.is_null()) { + debug_octant_line_material.instantiate(); + debug_octant_line_material->set_shading_mode(StandardMaterial3D::SHADING_MODE_UNSHADED); + debug_octant_line_material->set_transparency(StandardMaterial3D::TRANSPARENCY_ALPHA); + debug_octant_line_material->set_flag(StandardMaterial3D::FLAG_DISABLE_FOG, true); + debug_octant_line_material->set_albedo(debug_octant_color); + } + + Array debug_mesh_arrays = _build_octant_line_mesh_arrays(); + + if (debug_octant_line_mesh_rid.is_null()) { + debug_octant_line_mesh_rid = RS::get_singleton()->mesh_create(); + } + RS::get_singleton()->mesh_clear(debug_octant_line_mesh_rid); + RS::get_singleton()->mesh_add_surface_from_arrays(debug_octant_line_mesh_rid, RSE::PRIMITIVE_LINES, debug_mesh_arrays); + RS::get_singleton()->mesh_surface_set_material(debug_octant_line_mesh_rid, 0, debug_octant_line_material->get_rid()); + + RID debug_scenario = get_world_3d()->get_scenario(); + const Transform3D &gridmap_xform = get_global_transform(); + + for (const KeyValue &ele : octant_map) { + OctantKey octant_key = ele.key; + HashMap::Iterator E = debug_octant_map.find(octant_key); + if (!E) { + OctantDebug *octant_debug = memnew(OctantDebug); + + RID debug_octant_line_instance_rid = RS::get_singleton()->instance_create(); + + RS::get_singleton()->instance_set_base(debug_octant_line_instance_rid, debug_octant_line_mesh_rid); + RS::get_singleton()->instance_set_scenario(debug_octant_line_instance_rid, debug_scenario); + + octant_debug->debug_line_instance_rid = debug_octant_line_instance_rid; + + E = debug_octant_map.insert(octant_key, octant_debug); + } + + OctantDebug &octant_debug = *E->value; + const Transform3D octant_transform = gridmap_xform * (Transform3D(Basis(), Vector3(octant_key.x, octant_key.y, octant_key.z) * octant_size * cell_size)); + + RS::get_singleton()->instance_set_scenario(octant_debug.debug_line_instance_rid, debug_scenario); + RS::get_singleton()->instance_set_transform(octant_debug.debug_line_instance_rid, octant_transform); + RS::get_singleton()->instance_set_visible(octant_debug.debug_line_instance_rid, is_visible_in_tree()); + } +} + +void GridMap::_debug_clear_octants() { + for (const KeyValue &ele : octant_map) { + OctantKey octant_key = ele.key; + HashMap::Iterator E = debug_octant_map.find(octant_key); + if (E) { + OctantDebug &octant_debug = *E->value; + + if (octant_debug.debug_line_mesh_rid.is_valid()) { + RS::get_singleton()->free_rid(octant_debug.debug_line_mesh_rid); + octant_debug.debug_line_mesh_rid = RID(); + } + if (octant_debug.debug_line_instance_rid.is_valid()) { + RS::get_singleton()->free_rid(octant_debug.debug_line_instance_rid); + octant_debug.debug_line_instance_rid = RID(); + } + + memdelete(&octant_debug); + } + } + + debug_octant_map.clear(); +} + +Array GridMap::_build_octant_line_mesh_arrays() const { + AABB aabb; + aabb.size = octant_size * cell_size; + + Vector vertex_array; + vertex_array.resize(24); + Vector3 *vertex_array_ptrw = vertex_array.ptrw(); + int vertex_index = 0; + + for (int i = 0; i < 12; i++) { + Vector3 a, b; + aabb.get_edge(i, a, b); + vertex_array_ptrw[vertex_index++] = a; + vertex_array_ptrw[vertex_index++] = b; + } + + Array mesh_arrays; + mesh_arrays.resize(Mesh::ARRAY_MAX); + mesh_arrays[Mesh::ARRAY_VERTEX] = vertex_array; + + return mesh_arrays; +} +#endif // DEBUG_ENABLED + void GridMap::set_cell_item(const Vector3i &p_position, int p_item, int p_rot) { if (baked_meshes.size() && !recreating_octants) { //if you set a cell item, baked meshes go good bye @@ -991,6 +1119,8 @@ void GridMap::_octant_exit_world(const OctantKey &p_key) { g.navigation_debug_edge_connections_mesh.unref(); } } + + _debug_clear_octants(); #endif // DEBUG_ENABLED } @@ -1040,9 +1170,11 @@ void GridMap::_octant_clean_up(const OctantKey &p_key) { g.navigation_debug_edge_connections_mesh.unref(); } } + + _debug_clear_octants(); #endif // DEBUG_ENABLED - //erase multimeshes + // Erase multimeshes. for (int i = 0; i < g.multimesh_instances.size(); i++) { RS::get_singleton()->free_rid(g.multimesh_instances[i].instance); @@ -1067,20 +1199,32 @@ void GridMap::_notification(int p_what) { } break; case NOTIFICATION_ENTER_TREE: { -#if defined(DEBUG_ENABLED) && !defined(NAVIGATION_3D_DISABLED) +#ifdef DEBUG_ENABLED + _debug_update(); + +#ifndef NAVIGATION_3D_DISABLED if (bake_navigation && NavigationServer3D::get_singleton()->get_debug_navigation_enabled()) { _update_navigation_debug_edge_connections(); } -#endif // defined(DEBUG_ENABLED) && !defined(NAVIGATION_3D_DISABLED) +#endif // NAVIGATION_3D_DISABLED + +#endif // DEBUG_ENABLED + _update_visibility(); } break; +#ifdef DEBUG_ENABLED + case NOTIFICATION_EXIT_TREE: { + _debug_clear_octants(); + } break; +#endif + case NOTIFICATION_TRANSFORM_CHANGED: { Transform3D new_xform = get_global_transform(); if (new_xform == last_transform) { break; } - //update run + for (const KeyValue &E : octant_map) { _octant_transform(E.key); } @@ -1090,6 +1234,17 @@ void GridMap::_notification(int p_what) { for (int i = 0; i < baked_meshes.size(); i++) { RS::get_singleton()->instance_set_transform(baked_meshes[i].instance, get_global_transform()); } + +#ifdef DEBUG_ENABLED + for (const KeyValue &E : debug_octant_map) { + OctantKey octant_key = E.key; + OctantDebug &octant_debug = *E.value; + if (octant_debug.debug_line_instance_rid.is_valid()) { + const Transform3D octant_transform = new_xform * (Transform3D(Basis(), Vector3(octant_key.x, octant_key.y, octant_key.z) * octant_size * cell_size)); + RS::get_singleton()->instance_set_transform(octant_debug.debug_line_instance_rid, octant_transform); + } + } +#endif } break; case NOTIFICATION_EXIT_WORLD: { @@ -1097,9 +1252,6 @@ void GridMap::_notification(int p_what) { _octant_exit_world(E.key); } - //_queue_octants_dirty(MAP_DIRTY_INSTANCES|MAP_DIRTY_TRANSFORMS); - //_update_octants_callback(); - //_update_area_instances(); for (int i = 0; i < baked_meshes.size(); i++) { RS::get_singleton()->instance_set_scenario(baked_meshes[i].instance, RID()); } @@ -1127,6 +1279,15 @@ void GridMap::_update_visibility() { for (int i = 0; i < baked_meshes.size(); i++) { RS::get_singleton()->instance_set_visible(baked_meshes[i].instance, is_visible_in_tree()); } + +#ifdef DEBUG_ENABLED + for (const KeyValue &E : debug_octant_map) { + OctantDebug &octant_debug = *E.value; + if (octant_debug.debug_line_instance_rid.is_valid()) { + RS::get_singleton()->instance_set_visible(octant_debug.debug_line_instance_rid, is_visible_in_tree()); + } + } +#endif } void GridMap::_queue_octants_dirty() { @@ -1140,12 +1301,17 @@ void GridMap::_queue_octants_dirty() { void GridMap::_recreate_octant_data() { recreating_octants = true; + HashMap cell_copy(cell_map); _clear_internal(); for (const KeyValue &E : cell_copy) { set_cell_item(Vector3i(E.key), E.value.item, E.value.rot); } + recreating_octants = false; +#ifdef DEBUG_ENABLED + _debug_update(); +#endif } void GridMap::_clear_internal() { @@ -1160,6 +1326,10 @@ void GridMap::_clear_internal() { octant_map.clear(); cell_map.clear(); +#ifdef DEBUG_ENABLED + _debug_clear_octants(); + _debug_update(); +#endif } void GridMap::clear() { @@ -1194,6 +1364,10 @@ void GridMap::_update_octants_callback() { _update_visibility(); awaiting_update = false; + +#ifdef DEBUG_ENABLED + _debug_update_octants(); +#endif } void GridMap::_bind_methods() { @@ -1284,6 +1458,12 @@ void GridMap::_bind_methods() { ClassDB::bind_method(D_METHOD("clear_baked_meshes"), &GridMap::clear_baked_meshes); ClassDB::bind_method(D_METHOD("make_baked_meshes", "gen_lightmap_uv", "lightmap_uv_texel_size"), &GridMap::make_baked_meshes, DEFVAL(false), DEFVAL(0.1)); + ClassDB::bind_method(D_METHOD("set_debug_show_octants", "enable"), &GridMap::set_debug_show_octants); + ClassDB::bind_method(D_METHOD("get_debug_show_octants"), &GridMap::get_debug_show_octants); + + ClassDB::bind_method(D_METHOD("set_debug_octant_color", "color"), &GridMap::set_debug_octant_color); + ClassDB::bind_method(D_METHOD("get_debug_octant_color"), &GridMap::get_debug_octant_color); + ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "mesh_library", PROPERTY_HINT_RESOURCE_TYPE, MeshLibrary::get_class_static()), "set_mesh_library", "get_mesh_library"); #ifndef PHYSICS_3D_DISABLED ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "physics_material", PROPERTY_HINT_RESOURCE_TYPE, PhysicsMaterial::get_class_static()), "set_physics_material", "get_physics_material"); @@ -1304,6 +1484,9 @@ void GridMap::_bind_methods() { #endif // PHYSICS_3D_DISABLED ADD_GROUP("Navigation", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bake_navigation"), "set_bake_navigation", "is_baking_navigation"); + ADD_GROUP("Debug", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "debug_show_octants"), "set_debug_show_octants", "get_debug_show_octants"); + ADD_PROPERTY(PropertyInfo(Variant::COLOR, "debug_octant_color"), "set_debug_octant_color", "get_debug_octant_color"); BIND_CONSTANT(INVALID_CELL_ITEM); @@ -1689,6 +1872,21 @@ RID GridMap::get_bake_mesh_instance(int p_idx) { return baked_meshes[p_idx].instance; } +void GridMap::set_debug_show_octants(bool p_enable) { +#ifdef DEBUG_ENABLED + if (debug_show_octants == p_enable) { + return; + } + + debug_show_octants = p_enable; + _debug_update(); +#endif +} + +bool GridMap::get_debug_show_octants() const { + return debug_show_octants; +} + GridMap::GridMap() { set_notify_transform(true); #if defined(DEBUG_ENABLED) && !defined(NAVIGATION_3D_DISABLED) @@ -1857,10 +2055,16 @@ void GridMap::_navigation_map_changed(RID p_map) { GridMap::~GridMap() { clear(); -#if defined(DEBUG_ENABLED) && !defined(NAVIGATION_3D_DISABLED) + +#ifdef DEBUG_ENABLED + _debug_clear_octants(); + +#ifndef NAVIGATION_3D_DISABLED NavigationServer3D::get_singleton()->disconnect("map_changed", callable_mp(this, &GridMap::_navigation_map_changed)); NavigationServer3D::get_singleton()->disconnect("navigation_debug_changed", callable_mp(this, &GridMap::_update_navigation_debug_edge_connections)); -#endif // defined(DEBUG_ENABLED) && !defined(NAVIGATION_3D_DISABLED) +#endif // NAVIGATION_3D_DISABLED + +#endif // DEBUG_ENABLED } #if defined(DEBUG_ENABLED) && !defined(NAVIGATION_3D_DISABLED) diff --git a/modules/gridmap/grid_map.h b/modules/gridmap/grid_map.h index dc7b8837fd..44fb3a82f4 100644 --- a/modules/gridmap/grid_map.h +++ b/modules/gridmap/grid_map.h @@ -233,6 +233,27 @@ class GridMap : public Node3D { Vector baked_meshes; + bool debug_show_octants = false; + Color debug_octant_color = Color(1.0, 1.0, 1.0, 1.0); +#ifdef DEBUG_ENABLED + bool debug_dirty = false; + Ref debug_octant_line_material; + + void _debug_update(); + void _debug_update_octants(); + void _debug_clear_octants(); + + RID debug_octant_line_mesh_rid; + + struct OctantDebug { + RID debug_line_mesh_rid; + RID debug_line_instance_rid; + }; + HashMap debug_octant_map; + + Array _build_octant_line_mesh_arrays() const; +#endif // DEBUG_ENABLED + protected: bool _set(const StringName &p_name, const Variant &p_value); bool _get(const StringName &p_name, Variant &r_ret) const; @@ -337,6 +358,12 @@ public: Array get_bake_meshes(); RID get_bake_mesh_instance(int p_idx); + void set_debug_show_octants(bool p_enable); + bool get_debug_show_octants() const; + + void set_debug_octant_color(const Color &p_color); + Color get_debug_octant_color() const; + #ifndef NAVIGATION_3D_DISABLED private: static Callable _navmesh_source_geometry_parsing_callback;