Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix TileMapLayer navmesh baking #91464

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 72 additions & 103 deletions modules/navigation/2d/nav_mesh_generator_2d.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_
generator_parse_multimeshinstance2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_polygon2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_staticbody2d_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_tilemap_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_tile_map_layer_node(p_navigation_mesh, p_source_geometry_data, p_node);
generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node);

generator_rid_rwlock.read_lock();
Expand All @@ -259,6 +259,14 @@ void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_
for (int i = 0; i < p_node->get_child_count(); i++) {
generator_parse_geometry_node(p_navigation_mesh, p_source_geometry_data, p_node->get_child(i), p_recurse_children);
}
} else if (Object::cast_to<TileMap>(p_node)) {
// Special case for TileMap, so that internal layer get parsed even if p_recurse_children is false.
for (int i = 0; i < p_node->get_child_count(); i++) {
TileMapLayer *tile_map_layer = Object::cast_to<TileMapLayer>(p_node->get_child(i));
if (tile_map_layer->get_index_in_tile_map() >= 0) {
generator_parse_tile_map_layer_node(p_navigation_mesh, p_source_geometry_data, tile_map_layer);
}
}
}
}

Expand Down Expand Up @@ -580,141 +588,102 @@ void NavMeshGenerator2D::generator_parse_staticbody2d_node(const Ref<NavigationP
}
}

void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
TileMap *tilemap = Object::cast_to<TileMap>(p_node);

if (tilemap == nullptr) {
void NavMeshGenerator2D::generator_parse_tile_map_layer_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
TileMapLayer *tile_map_layer = Object::cast_to<TileMapLayer>(p_node);
if (tile_map_layer == nullptr) {
return;
}

NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask();

if (tilemap->get_layers_count() <= 0) {
return;
}

Ref<TileSet> tile_set = tilemap->get_tileset();
Ref<TileSet> tile_set = tile_map_layer->get_tile_set();
if (!tile_set.is_valid()) {
return;
}

int physics_layers_count = tile_set->get_physics_layers_count();
int navigation_layers_count = tile_set->get_navigation_layers_count();

if (physics_layers_count <= 0 && navigation_layers_count <= 0) {
return;
}

HashSet<Vector2i> cells_with_navigation_polygon;
HashSet<Vector2i> cells_with_collision_polygon;
NavigationPolygon::ParsedGeometryType parsed_geometry_type = p_navigation_mesh->get_parsed_geometry_type();
uint32_t parsed_collision_mask = p_navigation_mesh->get_parsed_collision_mask();

const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tile_map_layer->get_global_transform();

const Transform2D tilemap_xform = p_source_geometry_data->root_node_transform * tilemap->get_global_transform();
TypedArray<Vector2i> used_cells = tile_map_layer->get_used_cells();
for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) {
const Vector2i &cell = used_cells[used_cell_index];

#ifdef DEBUG_ENABLED
int error_print_counter = 0;
int error_print_max = 10;
#endif // DEBUG_ENABLED
const TileData *tile_data = tile_map_layer->get_cell_tile_data(cell);
if (tile_data == nullptr) {
continue;
}

for (int tilemap_layer = 0; tilemap_layer < tilemap->get_layers_count(); tilemap_layer++) {
TypedArray<Vector2i> used_cells = tilemap->get_used_cells(tilemap_layer);
// Transform flags.
const int alternative_id = tile_map_layer->get_cell_alternative_tile(cell);
bool flip_h = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H);
bool flip_v = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V);
bool transpose = (alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE);

Transform2D tile_transform;
tile_transform.set_origin(tile_map_layer->map_to_local(cell));

const Transform2D tile_transform_offset = tilemap_xform * tile_transform;

// Parse traversable polygons.
for (int navigation_layer = 0; navigation_layer < navigation_layers_count; navigation_layer++) {
Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(navigation_layer, flip_h, flip_v, transpose);
if (navigation_polygon.is_valid()) {
for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) {
const Vector<Vector2> &navigation_polygon_outline = navigation_polygon->get_outline(outline_index);
if (navigation_polygon_outline.is_empty()) {
continue;
}

for (int used_cell_index = 0; used_cell_index < used_cells.size(); used_cell_index++) {
const Vector2i &cell = used_cells[used_cell_index];
Vector<Vector2> traversable_outline;
traversable_outline.resize(navigation_polygon_outline.size());

const TileData *tile_data = tilemap->get_cell_tile_data(tilemap_layer, cell, false);
if (tile_data == nullptr) {
continue;
}
const Vector2 *navigation_polygon_outline_ptr = navigation_polygon_outline.ptr();
Vector2 *traversable_outline_ptrw = traversable_outline.ptrw();

// Transform flags.
const int alternative_id = tilemap->get_cell_alternative_tile(tilemap_layer, cell, false);
bool flip_h = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_H);
bool flip_v = (alternative_id & TileSetAtlasSource::TRANSFORM_FLIP_V);
bool transpose = (alternative_id & TileSetAtlasSource::TRANSFORM_TRANSPOSE);

Transform2D tile_transform;
tile_transform.set_origin(tilemap->map_to_local(cell));

const Transform2D tile_transform_offset = tilemap_xform * tile_transform;

if (navigation_layers_count > 0) {
Ref<NavigationPolygon> navigation_polygon = tile_data->get_navigation_polygon(tilemap_layer, flip_h, flip_v, transpose);
if (navigation_polygon.is_valid()) {
if (cells_with_navigation_polygon.has(cell)) {
#ifdef DEBUG_ENABLED
error_print_counter++;
if (error_print_counter <= error_print_max) {
WARN_PRINT(vformat("TileMap navigation mesh baking error. The TileMap cell key Vector2i(%s, %s) has navigation mesh from 2 or more different TileMap layers assigned. This can cause unexpected navigation mesh baking results. The duplicated cell data was ignored.", cell.x, cell.y));
}
#endif // DEBUG_ENABLED
} else {
cells_with_navigation_polygon.insert(cell);

for (int outline_index = 0; outline_index < navigation_polygon->get_outline_count(); outline_index++) {
const Vector<Vector2> &navigation_polygon_outline = navigation_polygon->get_outline(outline_index);
if (navigation_polygon_outline.size() == 0) {
continue;
}

Vector<Vector2> traversable_outline;
traversable_outline.resize(navigation_polygon_outline.size());

const Vector2 *navigation_polygon_outline_ptr = navigation_polygon_outline.ptr();
Vector2 *traversable_outline_ptrw = traversable_outline.ptrw();

for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) {
traversable_outline_ptrw[traversable_outline_index] = tile_transform_offset.xform(navigation_polygon_outline_ptr[traversable_outline_index]);
}

p_source_geometry_data->_add_traversable_outline(traversable_outline);
}
for (int traversable_outline_index = 0; traversable_outline_index < traversable_outline.size(); traversable_outline_index++) {
traversable_outline_ptrw[traversable_outline_index] = tile_transform_offset.xform(navigation_polygon_outline_ptr[traversable_outline_index]);
}

p_source_geometry_data->_add_traversable_outline(traversable_outline);
}
}
}

if (physics_layers_count > 0 && (parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) && (tile_set->get_physics_layer_collision_layer(tilemap_layer) & parsed_collision_mask)) {
if (cells_with_collision_polygon.has(cell)) {
#ifdef DEBUG_ENABLED
error_print_counter++;
if (error_print_counter <= error_print_max) {
WARN_PRINT(vformat("TileMap navigation mesh baking error. The cell key Vector2i(%s, %s) has collision polygons from 2 or more different TileMap layers assigned that all match the parsed collision mask. This can cause unexpected navigation mesh baking results. The duplicated cell data was ignored.", cell.x, cell.y));
// Parse obstacles.
for (int physics_layer = 0; physics_layer < physics_layers_count; physics_layer++) {
if ((parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_STATIC_COLLIDERS || parsed_geometry_type == NavigationPolygon::PARSED_GEOMETRY_BOTH) &&
(tile_set->get_physics_layer_collision_layer(physics_layer) & parsed_collision_mask)) {
for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(physics_layer); collision_polygon_index++) {
PackedVector2Array collision_polygon_points = tile_data->get_collision_polygon_points(physics_layer, collision_polygon_index);
if (collision_polygon_points.is_empty()) {
continue;
}
#endif // DEBUG_ENABLED
} else {
cells_with_collision_polygon.insert(cell);

for (int collision_polygon_index = 0; collision_polygon_index < tile_data->get_collision_polygons_count(tilemap_layer); collision_polygon_index++) {
PackedVector2Array collision_polygon_points = tile_data->get_collision_polygon_points(tilemap_layer, collision_polygon_index);
if (collision_polygon_points.size() == 0) {
continue;
}

if (flip_h || flip_v || transpose) {
collision_polygon_points = TileData::get_transformed_vertices(collision_polygon_points, flip_h, flip_v, transpose);
}

Vector<Vector2> obstruction_outline;
obstruction_outline.resize(collision_polygon_points.size());
if (flip_h || flip_v || transpose) {
collision_polygon_points = TileData::get_transformed_vertices(collision_polygon_points, flip_h, flip_v, transpose);
}

const Vector2 *collision_polygon_points_ptr = collision_polygon_points.ptr();
Vector2 *obstruction_outline_ptrw = obstruction_outline.ptrw();
Vector<Vector2> obstruction_outline;
obstruction_outline.resize(collision_polygon_points.size());

for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) {
obstruction_outline_ptrw[obstruction_outline_index] = tile_transform_offset.xform(collision_polygon_points_ptr[obstruction_outline_index]);
}
const Vector2 *collision_polygon_points_ptr = collision_polygon_points.ptr();
Vector2 *obstruction_outline_ptrw = obstruction_outline.ptrw();

p_source_geometry_data->_add_obstruction_outline(obstruction_outline);
for (int obstruction_outline_index = 0; obstruction_outline_index < obstruction_outline.size(); obstruction_outline_index++) {
obstruction_outline_ptrw[obstruction_outline_index] = tile_transform_offset.xform(collision_polygon_points_ptr[obstruction_outline_index]);
}

p_source_geometry_data->_add_obstruction_outline(obstruction_outline);
}
}
}
}
#ifdef DEBUG_ENABLED
if (error_print_counter > error_print_max) {
ERR_PRINT(vformat("TileMap navigation mesh baking error. A total of %s cells with navigation or collision polygons from 2 or more different TileMap layers overlap. This can cause unexpected navigation mesh baking results. The duplicated cell data was ignored.", error_print_counter));
}
#endif // DEBUG_ENABLED
}

void NavMeshGenerator2D::generator_parse_navigationobstacle_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) {
Expand Down
2 changes: 1 addition & 1 deletion modules/navigation/2d/nav_mesh_generator_2d.h
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ class NavMeshGenerator2D : public Object {
static void generator_parse_multimeshinstance2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
static void generator_parse_polygon2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
static void generator_parse_staticbody2d_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
static void generator_parse_tilemap_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
static void generator_parse_tile_map_layer_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
static void generator_parse_navigationobstacle_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);

static bool generator_emit_callback(const Callable &p_callback);
Expand Down
Loading