Make navmesh rasterization errors more lenient

Make navmesh rasterization on the navigation regions and map more lenient by starting out with a lower internal cell scale by default and changing the merge error to just warning that can be toggled.
This commit is contained in:
smix8 2025-09-23 22:59:07 +02:00
parent a078895ad2
commit 19df15f1dc
12 changed files with 74 additions and 15 deletions

View file

@ -1733,6 +1733,14 @@ ProjectSettings::ProjectSettings() {
GLOBAL_DEF("navigation/baking/thread_model/baking_use_multiple_threads", true);
GLOBAL_DEF("navigation/baking/thread_model/baking_use_high_priority_threads", true);
#endif // !defined(NAVIGATION_2D_DISABLED) || !defined(NAVIGATION_3D_DISABLED)
#ifndef NAVIGATION_2D_DISABLED
GLOBAL_DEF("navigation/2d/warnings/navmesh_edge_merge_errors", true);
GLOBAL_DEF("navigation/2d/warnings/navmesh_cell_size_mismatch", true);
#endif // NAVIGATION_2D_DISABLED
#ifndef NAVIGATION_3D_DISABLED
GLOBAL_DEF("navigation/3d/warnings/navmesh_edge_merge_errors", true);
GLOBAL_DEF("navigation/3d/warnings/navmesh_cell_size_mismatch", true);
#endif // NAVIGATION_3D_DISABLED
ProjectSettings::get_singleton()->add_hidden_prefix("input/");
}

View file

@ -2314,6 +2314,12 @@
<member name="navigation/2d/use_edge_connections" type="bool" setter="" getter="" default="true">
If enabled 2D navigation regions will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin. This setting only affects World2D default navigation maps.
</member>
<member name="navigation/2d/warnings/navmesh_cell_size_mismatch" type="bool" setter="" getter="" default="true">
If [code]true[/code], the navigation system will print warnings when a navigation mesh with a small cell size is used on a navigation map with a larger size as this commonly causes rasterization errors.
</member>
<member name="navigation/2d/warnings/navmesh_edge_merge_errors" type="bool" setter="" getter="" default="true">
If [code]true[/code], the navigation system will print warnings about navigation mesh edge merge errors occurring in navigation regions or maps.
</member>
<member name="navigation/3d/default_cell_height" type="float" setter="" getter="" default="0.25">
Default cell height for 3D navigation maps. See [method NavigationServer3D.map_set_cell_height].
</member>
@ -2335,6 +2341,12 @@
<member name="navigation/3d/use_edge_connections" type="bool" setter="" getter="" default="true">
If enabled 3D navigation regions will use edge connections to connect with other navigation regions within proximity of the navigation map edge connection margin. This setting only affects World3D default navigation maps.
</member>
<member name="navigation/3d/warnings/navmesh_cell_size_mismatch" type="bool" setter="" getter="" default="true">
If [code]true[/code], the navigation system will print warnings when a navigation mesh with a small cell size (or in 3D height) is used on a navigation map with a larger size as this commonly causes rasterization errors.
</member>
<member name="navigation/3d/warnings/navmesh_edge_merge_errors" type="bool" setter="" getter="" default="true">
If [code]true[/code], the navigation system will print warnings about navigation mesh edge merge errors occurring in navigation regions or maps.
</member>
<member name="navigation/avoidance/thread_model/avoidance_use_high_priority_threads" type="bool" setter="" getter="" default="true">
If enabled and avoidance calculations use multiple threads the threads run with high priority.
</member>

View file

@ -37,6 +37,8 @@
#include "nav_map_iteration_2d.h"
#include "nav_region_iteration_2d.h"
#include "core/config/project_settings.h"
using namespace Nav2D;
PointKey NavMapBuilder2D::get_point_key(const Vector2 &p_pos, const Vector2 &p_cell_size) {
@ -110,6 +112,7 @@ void NavMapBuilder2D::_build_step_find_edge_connection_pairs(NavMapIterationBuil
connection_pairs_map.clear();
connection_pairs_map.reserve(polygon_count);
int free_edges_count = 0; // How many ConnectionPairs have only one Connection.
int edge_merge_error_count = 0;
for (const Ref<NavRegionIteration2D> &region : map_iteration->region_iterations) {
for (const ConnectableEdge &connectable_edge : region->get_external_edges()) {
@ -138,11 +141,15 @@ void NavMapBuilder2D::_build_step_find_edge_connection_pairs(NavMapIterationBuil
} else {
// The edge is already connected with another edge, skip.
ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'. If you're certain none of above is the case, change 'navigation/2d/merge_rasterizer_cell_scale' to 0.001.");
edge_merge_error_count++;
}
}
}
if (edge_merge_error_count > 0 && GLOBAL_GET_CACHED(bool, "navigation/2d/warnings/navmesh_edge_merge_errors")) {
WARN_PRINT("Navigation map synchronization had " + itos(edge_merge_error_count) + " edge error(s).\nMore than 2 edges tried to occupy the same map rasterization space.\nThis causes a logical error in the navigation mesh geometry and is commonly caused by overlap or too densely placed edges.\nConsider baking with a higher 'cell_size', greater geometry margin, and less detailed bake objects to cause fewer edges.\nConsider lowering the 'navigation/2d/merge_rasterizer_cell_scale' in the project settings.\nThis warning can be toggled under 'navigation/2d/warnings/navmesh_edge_merge_errors' in the project settings.");
}
r_build.free_edge_count = free_edges_count;
}

View file

@ -35,6 +35,8 @@
#include "../triangle2.h"
#include "nav_region_iteration_2d.h"
#include "core/config/project_settings.h"
using namespace Nav2D;
void NavRegionBuilder2D::build_iteration(NavRegionIterationBuild2D &r_build) {
@ -179,6 +181,7 @@ void NavRegionBuilder2D::_build_step_find_edge_connection_pairs(NavRegionIterati
region_iteration->external_edges.clear();
int free_edges_count = 0;
int edge_merge_error_count = 0;
for (Polygon &poly : region_iteration->navmesh_polygons) {
for (uint32_t p = 0; p < poly.vertices.size(); p++) {
@ -208,11 +211,15 @@ void NavRegionBuilder2D::_build_step_find_edge_connection_pairs(NavRegionIterati
} else {
// The edge is already connected with another edge, skip.
ERR_FAIL_COND_MSG(pair.size >= 2, "Navigation region synchronization error. More than 2 edges tried to occupy the same map rasterization space. This is a logical error in the navigation mesh caused by overlap or too densely placed edges.");
edge_merge_error_count++;
}
}
}
if (edge_merge_error_count > 0 && GLOBAL_GET_CACHED(bool, "navigation/2d/warnings/navmesh_edge_merge_errors")) {
WARN_PRINT("Navigation region synchronization had " + itos(edge_merge_error_count) + " edge error(s).\nMore than 2 edges tried to occupy the same map rasterization space.\nThis causes a logical error in the navigation mesh geometry and is commonly caused by overlap or too densely placed edges.\nConsider baking with a higher 'cell_size', greater geometry margin, and less detailed bake objects to cause fewer edges.\nConsider lowering the 'navigation/2d/merge_rasterizer_cell_scale' in the project settings.\nThis warning can be toggled under 'navigation/2d/warnings/navmesh_edge_merge_errors' in the project settings.");
}
performance_data.pm_edge_free_count = free_edges_count;
}

View file

@ -80,7 +80,7 @@ void NavMap2D::set_merge_rasterizer_cell_scale(float p_value) {
if (merge_rasterizer_cell_scale == p_value) {
return;
}
merge_rasterizer_cell_scale = MAX(p_value, NavigationDefaults2D::NAV_MESH_CELL_SIZE_MIN);
merge_rasterizer_cell_scale = MAX(MIN(p_value, 0.1), NavigationDefaults2D::NAV_MESH_CELL_SIZE_MIN);
_update_merge_rasterizer_cell_dimensions();
map_settings_dirty = true;
}

View file

@ -56,7 +56,7 @@ class NavMap2D : public NavRid2D {
Vector2 merge_rasterizer_cell_size = Vector2(cell_size, cell_size);
// This value is used to control sensitivity of internal rasterizer.
float merge_rasterizer_cell_scale = 1.0;
float merge_rasterizer_cell_scale = 0.1;
bool use_edge_connections = true;
/// This value is used to detect the near edges to connect.

View file

@ -94,8 +94,13 @@ void NavRegion2D::set_transform(Transform2D p_transform) {
void NavRegion2D::set_navigation_mesh(Ref<NavigationPolygon> p_navigation_mesh) {
#ifdef DEBUG_ENABLED
if (map && p_navigation_mesh.is_valid() && !Math::is_equal_approx(double(map->get_cell_size()), double(p_navigation_mesh->get_cell_size()))) {
ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_size()), double(map->get_cell_size())));
if (map && p_navigation_mesh.is_valid() && GLOBAL_GET_CACHED(bool, "navigation/2d/warnings/navmesh_cell_size_mismatch")) {
const double map_cell_size = double(map->get_cell_size());
const double navmesh_cell_size = double(p_navigation_mesh->get_cell_size());
if (map_cell_size > navmesh_cell_size) {
WARN_PRINT(vformat("A navigation mesh that uses a `cell_size` of %s was assigned to a navigation map set to a larger `cell_size` of %s.\nThis mismatch in cell size can cause rasterization errors with navigation mesh edges on the navigation map.\nThe cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function.\nThe cell size for default navigation maps can also be changed in the project settings.\nThis warning can be toggled under 'navigation/2d/warnings/navmesh_cell_size_mismatch' in the project settings.", navmesh_cell_size, map_cell_size));
}
}
#endif // DEBUG_ENABLED

View file

@ -36,6 +36,8 @@
#include "nav_map_iteration_3d.h"
#include "nav_region_iteration_3d.h"
#include "core/config/project_settings.h"
using namespace Nav3D;
PointKey NavMapBuilder3D::get_point_key(const Vector3 &p_pos, const Vector3 &p_cell_size) {
@ -111,6 +113,7 @@ void NavMapBuilder3D::_build_step_find_edge_connection_pairs(NavMapIterationBuil
connection_pairs_map.clear();
connection_pairs_map.reserve(polygon_count);
int free_edges_count = 0; // How many ConnectionPairs have only one Connection.
int edge_merge_error_count = 0;
for (const Ref<NavRegionIteration3D> &region : map_iteration->region_iterations) {
for (const ConnectableEdge &connectable_edge : region->get_external_edges()) {
@ -139,11 +142,15 @@ void NavMapBuilder3D::_build_step_find_edge_connection_pairs(NavMapIterationBuil
} else {
// The edge is already connected with another edge, skip.
ERR_PRINT_ONCE("Navigation map synchronization error. Attempted to merge a navigation mesh polygon edge with another already-merged edge. This is usually caused by crossing edges, overlapping polygons, or a mismatch of the NavigationMesh / NavigationPolygon baked 'cell_size' and navigation map 'cell_size'. If you're certain none of above is the case, change 'navigation/3d/merge_rasterizer_cell_scale' to 0.001.");
edge_merge_error_count++;
}
}
}
if (edge_merge_error_count > 0 && GLOBAL_GET_CACHED(bool, "navigation/3d/warnings/navmesh_edge_merge_errors")) {
WARN_PRINT("Navigation map synchronization had " + itos(edge_merge_error_count) + " edge error(s).\nMore than 2 edges tried to occupy the same map rasterization space.\nThis causes a logical error in the navigation mesh geometry and is commonly caused by overlap or too densely placed edges.\nConsider baking with a higher 'cell_size', greater geometry margin, and less detailed bake objects to cause fewer edges.\nConsider lowering the 'navigation/3d/merge_rasterizer_cell_scale' in the project settings.\nThis warning can be toggled under 'navigation/3d/warnings/navmesh_edge_merge_errors' in the project settings.");
}
r_build.free_edge_count = free_edges_count;
}

View file

@ -34,6 +34,8 @@
#include "../nav_region_3d.h"
#include "nav_region_iteration_3d.h"
#include "core/config/project_settings.h"
using namespace Nav3D;
void NavRegionBuilder3D::build_iteration(NavRegionIterationBuild3D &r_build) {
@ -180,6 +182,7 @@ void NavRegionBuilder3D::_build_step_find_edge_connection_pairs(NavRegionIterati
region_iteration->external_edges.clear();
int free_edges_count = 0;
int edge_merge_error_count = 0;
for (Polygon &poly : region_iteration->navmesh_polygons) {
for (uint32_t p = 0; p < poly.vertices.size(); p++) {
@ -209,11 +212,15 @@ void NavRegionBuilder3D::_build_step_find_edge_connection_pairs(NavRegionIterati
} else {
// The edge is already connected with another edge, skip.
ERR_FAIL_COND_MSG(pair.size >= 2, "Navigation region synchronization error. More than 2 edges tried to occupy the same map rasterization space. This is a logical error in the navigation mesh caused by overlap or too densely placed edges.");
edge_merge_error_count++;
}
}
}
if (edge_merge_error_count > 0 && GLOBAL_GET_CACHED(bool, "navigation/3d/warnings/navmesh_edge_merge_errors")) {
WARN_PRINT("Navigation region synchronization had " + itos(edge_merge_error_count) + " edge error(s).\nMore than 2 edges tried to occupy the same map rasterization space.\nThis causes a logical error in the navigation mesh geometry and is commonly caused by overlap or too densely placed edges.\nConsider baking with a higher 'cell_size', greater geometry margin, and less detailed bake objects to cause fewer edges.\nConsider lowering the 'navigation/3d/merge_rasterizer_cell_scale' in the project settings.\nThis warning can be toggled under 'navigation/3d/warnings/navmesh_edge_merge_errors' in the project settings.");
}
performance_data.pm_edge_free_count = free_edges_count;
}

View file

@ -97,7 +97,7 @@ void NavMap3D::set_merge_rasterizer_cell_scale(float p_value) {
if (merge_rasterizer_cell_scale == p_value) {
return;
}
merge_rasterizer_cell_scale = MAX(p_value, NavigationDefaults3D::NAV_MESH_CELL_SIZE_MIN);
merge_rasterizer_cell_scale = MAX(MIN(p_value, 0.1), NavigationDefaults3D::NAV_MESH_CELL_SIZE_MIN);
_update_merge_rasterizer_cell_dimensions();
map_settings_dirty = true;
}

View file

@ -62,7 +62,7 @@ class NavMap3D : public NavRid3D {
Vector3 merge_rasterizer_cell_size = Vector3(cell_size, cell_height, cell_size);
// This value is used to control sensitivity of internal rasterizer.
float merge_rasterizer_cell_scale = 1.0;
float merge_rasterizer_cell_scale = 0.1;
bool use_edge_connections = true;
/// This value is used to detect the near edges to connect.

View file

@ -100,12 +100,18 @@ void NavRegion3D::set_transform(Transform3D p_transform) {
void NavRegion3D::set_navigation_mesh(Ref<NavigationMesh> p_navigation_mesh) {
#ifdef DEBUG_ENABLED
if (map && p_navigation_mesh.is_valid() && !Math::is_equal_approx(double(map->get_cell_size()), double(p_navigation_mesh->get_cell_size()))) {
ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_size` of %s while assigned to a navigation map set to a `cell_size` of %s. The cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function. The cell size for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_size()), double(map->get_cell_size())));
}
if (map && p_navigation_mesh.is_valid() && GLOBAL_GET_CACHED(bool, "navigation/3d/warnings/navmesh_cell_size_mismatch")) {
const double map_cell_size = double(map->get_cell_size());
const double map_cell_height = double(map->get_cell_height());
const double navmesh_cell_size = double(p_navigation_mesh->get_cell_size());
const double navmesh_cell_height = double(p_navigation_mesh->get_cell_height());
if (map && p_navigation_mesh.is_valid() && !Math::is_equal_approx(double(map->get_cell_height()), double(p_navigation_mesh->get_cell_height()))) {
ERR_PRINT_ONCE(vformat("Attempted to update a navigation region with a navigation mesh that uses a `cell_height` of %s while assigned to a navigation map set to a `cell_height` of %s. The cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function. The cell height for default navigation maps can also be changed in the ProjectSettings.", double(p_navigation_mesh->get_cell_height()), double(map->get_cell_height())));
if (map_cell_size > navmesh_cell_size) {
WARN_PRINT(vformat("A navigation mesh that uses a `cell_size` of %s was assigned to a navigation map set to a larger `cell_size` of %s.\nThis mismatch in cell size can cause rasterization errors with navigation mesh edges on the navigation map.\nThe cell size for navigation maps can be changed by using the NavigationServer map_set_cell_size() function.\nThe cell size for default navigation maps can also be changed in the project settings.\nThis warning can be toggled under 'navigation/3d/warnings/navmesh_cell_size_mismatch' in the project settings.", navmesh_cell_size, map_cell_size));
}
if (map_cell_height > navmesh_cell_height) {
WARN_PRINT(vformat("A navigation mesh that uses a `cell_height` of %s was assigned to a navigation map set to a larger `cell_height` of %s.\nThis mismatch in cell height can cause rasterization errors with navigation mesh edges on the navigation map.\nThe cell height for navigation maps can be changed by using the NavigationServer map_set_cell_height() function.\nThe cell height for default navigation maps can also be changed in the project settings.\nThis warning can be toggled under 'navigation/3d/warnings/navmesh_cell_size_mismatch' in the project settings.", navmesh_cell_height, map_cell_height));
}
}
#endif // DEBUG_ENABLED