From 4765bc883c297836372b09c56a2789845583186a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gilles=20Roudi=C3=A8re?= Date: Mon, 10 Feb 2025 16:33:23 +0100 Subject: [PATCH] Chunk tilemap physics --- core/math/geometry_2d.cpp | 64 +++- core/math/geometry_2d.h | 5 +- doc/classes/TileMapLayer.xml | 9 +- scene/2d/tile_map_layer.cpp | 689 ++++++++++++++++++++++------------- scene/2d/tile_map_layer.h | 115 +++++- 5 files changed, 615 insertions(+), 267 deletions(-) diff --git a/core/math/geometry_2d.cpp b/core/math/geometry_2d.cpp index 376d5d0b43..9c8a771de3 100644 --- a/core/math/geometry_2d.cpp +++ b/core/math/geometry_2d.cpp @@ -38,17 +38,63 @@ const int clipper_precision = 5; // Based on CMP_EPSILON. const double clipper_scale = Math::pow(10.0, clipper_precision); -Vector> Geometry2D::decompose_polygon_in_convex(const Vector &polygon) { +void Geometry2D::merge_many_polygons(const Vector> &p_polygons, Vector> &r_out_polygons, Vector> &r_out_holes) { + using namespace Clipper2Lib; + + PathsD subjects; + for (const Vector &polygon : p_polygons) { + PathD path(polygon.size()); + for (int i = 0; i < polygon.size(); i++) { + const Vector2 &point = polygon[i]; + path[i] = PointD(point.x, point.y); + } + subjects.push_back(path); + } + + PathsD solution = Union(subjects, FillRule::NonZero); + solution = SimplifyPaths(solution, 0.01); + + r_out_polygons.clear(); + r_out_holes.clear(); + for (PathsD::size_type i = 0; i < solution.size(); ++i) { + PathD &path = solution[i]; + + Vector output_polygon; + output_polygon.resize(path.size()); + for (PathsD::size_type j = 0; j < path.size(); ++j) { + output_polygon.set(j, Vector2(static_cast(path[j].x), static_cast(path[j].y))); + } + if (IsPositive(path)) { + r_out_polygons.push_back(output_polygon); + } else { + r_out_holes.push_back(output_polygon); + } + } +} + +Vector> Geometry2D::decompose_many_polygons_in_convex(const Vector> &p_polygons, const Vector> &p_holes) { Vector> decomp; List in_poly, out_poly; - TPPLPoly inp; - inp.Init(polygon.size()); - for (int i = 0; i < polygon.size(); i++) { - inp.GetPoint(i) = polygon[i]; + for (const Vector &polygon : p_polygons) { + TPPLPoly inp; + inp.Init(polygon.size()); + for (int i = 0; i < polygon.size(); i++) { + inp.GetPoint(i) = polygon[i]; + } + inp.SetOrientation(TPPL_ORIENTATION_CCW); + in_poly.push_back(inp); + } + for (const Vector &polygon : p_holes) { + TPPLPoly inp; + inp.Init(polygon.size()); + for (int i = 0; i < polygon.size(); i++) { + inp.GetPoint(i) = polygon[i]; + } + inp.SetOrientation(TPPL_ORIENTATION_CW); + inp.SetHole(true); + in_poly.push_back(inp); } - inp.SetOrientation(TPPL_ORIENTATION_CCW); - in_poly.push_back(inp); TPPLPartition tpart; if (tpart.ConvexPartition_HM(&in_poly, &out_poly) == 0) { // Failed. ERR_PRINT("Convex decomposing failed!"); @@ -72,6 +118,10 @@ Vector> Geometry2D::decompose_polygon_in_convex(const Vector> Geometry2D::decompose_polygon_in_convex(const Vector &p_polygon) { + return Geometry2D::decompose_many_polygons_in_convex({ p_polygon }, {}); +} + struct _AtlasWorkRect { Size2i s; Point2i p; diff --git a/core/math/geometry_2d.h b/core/math/geometry_2d.h index abd395d8df..05e1626f2c 100644 --- a/core/math/geometry_2d.h +++ b/core/math/geometry_2d.h @@ -489,7 +489,10 @@ public: return points; } - static Vector> decompose_polygon_in_convex(const Vector &polygon); + static void merge_many_polygons(const Vector> &p_polygons, Vector> &r_out_polygons, Vector> &r_out_holes); + static Vector> decompose_many_polygons_in_convex(const Vector> &p_polygons, const Vector> &p_holes); + + static Vector> decompose_polygon_in_convex(const Vector &p_polygon); static void make_atlas(const Vector &p_rects, Vector &r_result, Size2i &r_size); static Vector partial_pack_rects(const Vector &p_sizes, const Size2i &p_atlas_size); diff --git a/doc/classes/TileMapLayer.xml b/doc/classes/TileMapLayer.xml index b17d3899ef..674c8e2ab6 100644 --- a/doc/classes/TileMapLayer.xml +++ b/doc/classes/TileMapLayer.xml @@ -114,7 +114,7 @@ - Returns the coordinates of the tile for given physics body [RID]. Such an [RID] can be retrieved from [method KinematicCollision2D.get_collider_rid], when colliding with a tile. + Returns the coordinates of the physics quadrant (see [member physics_quadrant_size]) for given physics body [RID]. Such an [RID] can be retrieved from [method KinematicCollision2D.get_collider_rid], when colliding with a tile. @@ -310,8 +310,13 @@ Enable or disable light occlusion. + + The [TileMapLayer]'s physics quadrant size. Within a physics quadrant, cells with similar physics properties are grouped together and their collision shapes get merged. [member physics_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quadrant size groups together [code]16 * 16 = 256[/code] tiles. + [b]Note:[/b] As quadrants are created according to the map's coordinate system, the quadrant's "square shape" might not look like square in the [TileMapLayer]'s local coordinate system. + [b]Note:[/b] This impacts the value returned by [method get_coords_for_body_rid]. + - The [TileMapLayer]'s quadrant size. A quadrant is a group of tiles to be drawn together on a single canvas item, for optimization purposes. [member rendering_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quadrant size groups together [code]16 * 16 = 256[/code] tiles. + The [TileMapLayer]'s rendering quadrant size. A quadrant is a group of tiles to be drawn together on a single canvas item, for optimization purposes. [member rendering_quadrant_size] defines the length of a square's side, in the map's coordinate system, that forms the quadrant. Thus, the default quadrant size groups together [code]16 * 16 = 256[/code] tiles. The quadrant size does not apply on a Y-sorted [TileMapLayer], as tiles are grouped by Y position instead in that case. [b]Note:[/b] As quadrants are created according to the map's coordinate system, the quadrant's "square shape" might not look like square in the [TileMapLayer]'s local coordinate system. diff --git a/scene/2d/tile_map_layer.cpp b/scene/2d/tile_map_layer.cpp index 56896233ec..6e918a2cce 100644 --- a/scene/2d/tile_map_layer.cpp +++ b/scene/2d/tile_map_layer.cpp @@ -31,6 +31,7 @@ #include "tile_map_layer.h" #include "core/io/marshalls.h" +#include "core/math/geometry_2d.h" #include "scene/2d/tile_map.h" #include "scene/gui/control.h" #include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h" @@ -40,16 +41,16 @@ Callable TileMapLayer::_navmesh_source_geometry_parsing_callback; RID TileMapLayer::_navmesh_source_geometry_parser; +Vector2i TileMapLayer::_coords_to_quadrant_coords(const Vector2i &p_coords, const int p_quadrant_size) const { + return Vector2i( + p_coords.x > 0 ? p_coords.x / p_quadrant_size : (p_coords.x - (p_quadrant_size - 1)) / p_quadrant_size, + p_coords.y > 0 ? p_coords.y / p_quadrant_size : (p_coords.y - (p_quadrant_size - 1)) / p_quadrant_size); +} + #ifdef DEBUG_ENABLED /////////////////////////////// Debug ////////////////////////////////////////// constexpr int TILE_MAP_DEBUG_QUADRANT_SIZE = 16; -Vector2i TileMapLayer::_coords_to_debug_quadrant_coords(const Vector2i &p_coords) const { - return Vector2i( - p_coords.x > 0 ? p_coords.x / TILE_MAP_DEBUG_QUADRANT_SIZE : (p_coords.x - (TILE_MAP_DEBUG_QUADRANT_SIZE - 1)) / TILE_MAP_DEBUG_QUADRANT_SIZE, - p_coords.y > 0 ? p_coords.y / TILE_MAP_DEBUG_QUADRANT_SIZE : (p_coords.y - (TILE_MAP_DEBUG_QUADRANT_SIZE - 1)) / TILE_MAP_DEBUG_QUADRANT_SIZE); -} - void TileMapLayer::_debug_update(bool p_force_cleanup) { RenderingServer *rs = RenderingServer::get_singleton(); @@ -78,106 +79,84 @@ void TileMapLayer::_debug_update(bool p_force_cleanup) { } } - // List all debug quadrants to update, creating new ones if needed. - SelfList::List dirty_debug_quadrant_list; - + // List all debug quadrants to update. + HashSet quadrants_to_updates; if (_debug_was_cleaned_up || anything_changed) { // Update all cells. for (KeyValue &kv : tile_map_layer_data) { CellData &cell_data = kv.value; - _debug_quadrants_update_cell(cell_data, dirty_debug_quadrant_list); + quadrants_to_updates.insert(_coords_to_quadrant_coords(cell_data.coords, TILE_MAP_DEBUG_QUADRANT_SIZE)); + // Physics quadrants are drawn from their origin. + Vector2i physics_quadrant_origin = _coords_to_quadrant_coords(cell_data.coords, physics_quadrant_size) * physics_quadrant_size; + quadrants_to_updates.insert(_coords_to_quadrant_coords(physics_quadrant_origin, TILE_MAP_DEBUG_QUADRANT_SIZE)); } } else { // Update dirty cells. for (SelfList *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { CellData &cell_data = *cell_data_list_element->self(); - _debug_quadrants_update_cell(cell_data, dirty_debug_quadrant_list); + quadrants_to_updates.insert(_coords_to_quadrant_coords(cell_data.coords, TILE_MAP_DEBUG_QUADRANT_SIZE)); + // Physics quadrants are drawn from their origin. + Vector2i physics_quadrant_origin = _coords_to_quadrant_coords(cell_data.coords, physics_quadrant_size) * physics_quadrant_size; + quadrants_to_updates.insert(_coords_to_quadrant_coords(physics_quadrant_origin, TILE_MAP_DEBUG_QUADRANT_SIZE)); } } // Update those quadrants. bool needs_set_not_interpolated = is_inside_tree() && get_tree()->is_physics_interpolation_enabled() && !is_physics_interpolated(); - for (SelfList *quadrant_list_element = dirty_debug_quadrant_list.first(); quadrant_list_element;) { - SelfList *next_quadrant_list_element = quadrant_list_element->next(); // "Hack" to clear the list while iterating. + for (const Vector2i &quadrant_coords : quadrants_to_updates) { + if (!debug_quadrant_map.has(quadrant_coords)) { + // Create a new quadrant and add it to the quadrant map. + Ref new_quadrant; + new_quadrant.instantiate(); + new_quadrant->quadrant_coords = quadrant_coords; + debug_quadrant_map[quadrant_coords] = new_quadrant; + } - DebugQuadrant &debug_quadrant = *quadrant_list_element->self(); + Ref debug_quadrant = debug_quadrant_map[quadrant_coords]; - // Check if the quadrant has a tile. - bool has_a_tile = false; - RID &ci = debug_quadrant.canvas_item; - for (SelfList *cell_data_list_element = debug_quadrant.cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + // Update the quadrant's canvas item. + RID &ci = debug_quadrant->canvas_item; + if (ci.is_valid()) { + rs->canvas_item_clear(ci); + } else { + ci = rs->canvas_item_create(); + if (needs_set_not_interpolated) { + rs->canvas_item_set_interpolated(ci, false); + } + rs->canvas_item_set_z_index(ci, RS::CANVAS_ITEM_Z_MAX - 1); + rs->canvas_item_set_parent(ci, get_canvas_item()); + } + const Vector2 quadrant_pos = tile_set->map_to_local(debug_quadrant->quadrant_coords * TILE_MAP_DEBUG_QUADRANT_SIZE); + Transform2D xform(0, quadrant_pos); + rs->canvas_item_set_transform(ci, xform); + + // Draw physics. + _physics_draw_quadrant_debug(ci, *debug_quadrant.ptr()); + + // Draw debug info. + for (SelfList *cell_data_list_element = debug_quadrant->cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { CellData &cell_data = *cell_data_list_element->self(); if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { - has_a_tile = true; - break; + _rendering_draw_cell_debug(ci, quadrant_pos, cell_data); + _navigation_draw_cell_debug(ci, quadrant_pos, cell_data); + _scenes_draw_cell_debug(ci, quadrant_pos, cell_data); + debug_quadrant->drawn_to = true; } } - if (has_a_tile) { - // Update the quadrant. - if (ci.is_valid()) { - rs->canvas_item_clear(ci); - } else { - ci = rs->canvas_item_create(); - if (needs_set_not_interpolated) { - rs->canvas_item_set_interpolated(ci, false); - } - rs->canvas_item_set_z_index(ci, RS::CANVAS_ITEM_Z_MAX - 1); - rs->canvas_item_set_parent(ci, get_canvas_item()); - } - - const Vector2 quadrant_pos = tile_set->map_to_local(debug_quadrant.quadrant_coords * TILE_MAP_DEBUG_QUADRANT_SIZE); - Transform2D xform(0, quadrant_pos); - rs->canvas_item_set_transform(ci, xform); - - for (SelfList *cell_data_list_element = debug_quadrant.cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { - CellData &cell_data = *cell_data_list_element->self(); - if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { - _rendering_draw_cell_debug(ci, quadrant_pos, cell_data); - _physics_draw_cell_debug(ci, quadrant_pos, cell_data); - _navigation_draw_cell_debug(ci, quadrant_pos, cell_data); - _scenes_draw_cell_debug(ci, quadrant_pos, cell_data); - } - } - } else { + // Free the quadrants that were not drawn to. + if (!debug_quadrant->drawn_to) { // Free the quadrant. if (ci.is_valid()) { rs->free(ci); } - quadrant_list_element->remove_from_list(); - debug_quadrant_map.erase(debug_quadrant.quadrant_coords); + debug_quadrant_map.erase(quadrant_coords); } - - quadrant_list_element = next_quadrant_list_element; } - dirty_debug_quadrant_list.clear(); - _debug_was_cleaned_up = false; } -void TileMapLayer::_debug_quadrants_update_cell(CellData &r_cell_data, SelfList::List &r_dirty_debug_quadrant_list) { - Vector2i quadrant_coords = _coords_to_debug_quadrant_coords(r_cell_data.coords); - - if (!debug_quadrant_map.has(quadrant_coords)) { - // Create a new quadrant and add it to the quadrant map. - Ref new_quadrant; - new_quadrant.instantiate(); - new_quadrant->quadrant_coords = quadrant_coords; - debug_quadrant_map[quadrant_coords] = new_quadrant; - } - - // Add the cell to its quadrant, if it is not already in there. - Ref &debug_quadrant = debug_quadrant_map[quadrant_coords]; - if (!r_cell_data.debug_quadrant_list_element.in_list()) { - debug_quadrant->cells.add(&r_cell_data.debug_quadrant_list_element); - } - - // Mark the quadrant as dirty. - if (!debug_quadrant->dirty_quadrant_list_element.in_list()) { - r_dirty_debug_quadrant_list.add(&debug_quadrant->dirty_quadrant_list_element); - } -} #endif // DEBUG_ENABLED /////////////////////////////// Rendering ////////////////////////////////////// @@ -499,9 +478,7 @@ void TileMapLayer::_rendering_quadrants_update_cell(CellData &r_cell_data, SelfL const Vector2i &coords = r_cell_data.coords; // Rounding down, instead of simply rounding towards zero (truncating). - quadrant_coords = Vector2i( - coords.x > 0 ? coords.x / rendering_quadrant_size : (coords.x - (rendering_quadrant_size - 1)) / rendering_quadrant_size, - coords.y > 0 ? coords.y / rendering_quadrant_size : (coords.y - (rendering_quadrant_size - 1)) / rendering_quadrant_size); + quadrant_coords = _coords_to_quadrant_coords(coords, rendering_quadrant_size); canvas_items_position = tile_set->map_to_local(rendering_quadrant_size * quadrant_coords); } @@ -702,31 +679,295 @@ void TileMapLayer::_rendering_draw_cell_debug(const RID &p_canvas_item, const Ve /////////////////////////////// Physics ////////////////////////////////////// void TileMapLayer::_physics_update(bool p_force_cleanup) { + PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); + // Check if we should cleanup everything. bool forced_cleanup = p_force_cleanup || !enabled || !collision_enabled || !is_inside_tree() || tile_set.is_null(); - if (forced_cleanup) { - // Clean everything. - for (KeyValue &kv : tile_map_layer_data) { - _physics_clear_cell(kv.value); + + // ----------- Quadrants processing ----------- + + // List all physics quadrants to update, creating new ones if needed. + SelfList::List dirty_physics_quadrant_list; + + // Check if anything changed that might change the quadrant shape. + // If so, recreate everything. + bool quadrant_shape_changed = dirty.flags[DIRTY_FLAGS_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_PHYSICS_QUADRANT_SIZE]; + + // Free all quadrants. + if (forced_cleanup || quadrant_shape_changed) { + for (const KeyValue> &kv : physics_quadrant_map) { + // Clear bodies. + for (KeyValue &kvbody : kv.value->bodies) { + if (kvbody.value.body.is_valid()) { + bodies_coords.erase(kvbody.value.body); + ps->free(kvbody.value.body); + } + } + kv.value->bodies.clear(); + kv.value->cells.clear(); } - } else { - if (_physics_was_cleaned_up || dirty.flags[DIRTY_FLAGS_TILE_SET] || dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES] || dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE]) { + physics_quadrant_map.clear(); + _physics_was_cleaned_up = true; + } + + if (!forced_cleanup) { + RID space = get_world_2d()->get_space(); + Transform2D gl_transform = get_global_transform(); + + // List all quadrants to update, recreating them if needed. + if (dirty.flags[DIRTY_FLAGS_LAYER_IN_TREE] || _physics_was_cleaned_up) { // Update all cells. for (KeyValue &kv : tile_map_layer_data) { - _physics_update_cell(kv.value); + CellData &cell_data = kv.value; + _physics_quadrants_update_cell(cell_data, dirty_physics_quadrant_list); } } else { // Update dirty cells. for (SelfList *cell_data_list_element = dirty.cell_list.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { CellData &cell_data = *cell_data_list_element->self(); - _physics_update_cell(cell_data); + _physics_quadrants_update_cell(cell_data, dirty_physics_quadrant_list); + } + } + + // Update all dirty quadrants. + for (SelfList *quadrant_list_element = dirty_physics_quadrant_list.first(); quadrant_list_element;) { + SelfList *next_quadrant_list_element = quadrant_list_element->next(); // "Hack" to clear the list while iterating. + + const Ref &physics_quadrant = quadrant_list_element->self(); + + // Check if the quadrant has a tile. + bool has_a_tile = false; + for (SelfList *cell_data_list_element = physics_quadrant->cells.first(); cell_data_list_element; cell_data_list_element = cell_data_list_element->next()) { + CellData &cell_data = *cell_data_list_element->self(); + if (cell_data.cell.source_id != TileSet::INVALID_SOURCE) { + has_a_tile = true; + break; + } + } + + if (has_a_tile) { + // Process the quadrant. + + // First, clear the quadrant bodies. + for (KeyValue &kvbody : physics_quadrant->bodies) { + RID &body = kvbody.value.body; + if (body.is_valid()) { + bodies_coords.erase(body); + ps->free(body); + body = RID(); + } + } + physics_quadrant->bodies.clear(); + physics_quadrant->shapes.clear(); + + // Quadrant origin + Vector2 quadrant_origin = tile_set->map_to_local(physics_quadrant->quadrant_coords); + + // Recreate the quadrant bodies. + for (uint32_t tile_set_physics_layer = 0; tile_set_physics_layer < (uint32_t)tile_set->get_physics_layers_count(); tile_set_physics_layer++) { + Ref physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer); + uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer); + uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer); + + // Merge polygons together for each quadrant. + for (SelfList *cell_data_quadrant_list_element = physics_quadrant->cells.first(); cell_data_quadrant_list_element; cell_data_quadrant_list_element = cell_data_quadrant_list_element->next()) { + CellData &cell_data = *cell_data_quadrant_list_element->self(); + + TileSetAtlasSource *atlas_source = Object::cast_to(*tile_set->get_source(cell_data.cell.source_id)); + + // Get the tile data. + const TileData *tile_data; + if (cell_data.runtime_tile_data_cache) { + tile_data = cell_data.runtime_tile_data_cache; + } else { + tile_data = atlas_source->get_tile_data(cell_data.cell.get_atlas_coords(), cell_data.cell.alternative_tile); + } + + // Transform flags. + bool flip_h = (cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); + bool flip_v = (cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); + bool transpose = (cell_data.cell.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); + + Vector2 linear_velocity = tile_data->get_constant_linear_velocity(tile_set_physics_layer); + real_t angular_velocity = tile_data->get_constant_angular_velocity(tile_set_physics_layer); + + // Setup polygons for merge. + for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(tile_set_physics_layer); polygon_index++) { + // Iterate over the polygons. + int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index); + + // Check if we need to create a new body. + PhysicsQuadrant::PhysicsBodyKey physics_body_key; + physics_body_key.physics_layer = tile_set_physics_layer; + physics_body_key.linear_velocity = linear_velocity; + physics_body_key.angular_velocity = angular_velocity; + physics_body_key.one_way_collision = tile_data->is_collision_polygon_one_way(tile_set_physics_layer, polygon_index); + physics_body_key.one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(tile_set_physics_layer, polygon_index); + + if (!physics_quadrant->bodies.has(physics_body_key)) { + RID body = ps->body_create(); + physics_quadrant->bodies[physics_body_key] = {}; + physics_quadrant->bodies[physics_body_key].body = body; + bodies_coords[body] = physics_quadrant->quadrant_coords; + + // Create or update the body. + ps->body_set_mode(body, use_kinematic_bodies ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + ps->body_set_space(body, space); + + Transform2D xform; + xform.set_origin(quadrant_origin); + xform = gl_transform * xform; + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); + + ps->body_attach_object_instance_id(body, tile_map_node ? tile_map_node->get_instance_id() : get_instance_id()); + ps->body_set_collision_layer(body, physics_layer); + ps->body_set_collision_mask(body, physics_mask); + ps->body_set_pickable(body, false); + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, linear_velocity); + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, angular_velocity); + + if (!physics_material.is_valid()) { + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); + } else { + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); + ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); + } + } + + for (int shape_index = 0; shape_index < shapes_count; shape_index++) { + Ref shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index, flip_h, flip_v, transpose); + + // Translate the polygon. + Vector convex_polygon = shape->get_points(); + for (int i = 0; i < convex_polygon.size(); i++) { + convex_polygon.set(i, convex_polygon[i] + tile_set->map_to_local(cell_data.coords) - quadrant_origin); + } + + physics_quadrant->bodies[physics_body_key].polygons.push_back(convex_polygon); + } + } + } + } + + // Iterate over the bodies + for (const KeyValue &kvbody : physics_quadrant->bodies) { + // Actually merge the polygons. + Vector> out_polygons; + Vector> out_holes; + Geometry2D::merge_many_polygons(kvbody.value.polygons, out_polygons, out_holes); + // Create shapes for each polygon. + int body_shape_index = 0; + Vector> convex_polygons = Geometry2D::decompose_many_polygons_in_convex(out_polygons, out_holes); + for (Vector &convex_polygon : convex_polygons) { + Ref shape; + shape.instantiate(); + shape->set_points(convex_polygon); + ps->body_add_shape(kvbody.value.body, shape->get_rid()); + ps->body_set_shape_as_one_way_collision(kvbody.value.body, body_shape_index, kvbody.key.one_way_collision, kvbody.key.one_way_collision_margin); + physics_quadrant->shapes.push_back(shape); + body_shape_index++; + } + } + } else { + // Free the quadrant. + for (KeyValue &kv : physics_quadrant->bodies) { + RID &body = kv.value.body; + if (body.is_valid()) { + bodies_coords.erase(body); + ps->free(body); + } + } + physics_quadrant->bodies.clear(); + physics_quadrant->cells.clear(); + physics_quadrant_map.erase(physics_quadrant->quadrant_coords); + } + + quadrant_list_element = next_quadrant_list_element; + } + + dirty_physics_quadrant_list.clear(); + + // Updates on physics changes. + if (dirty.flags[DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES]) { + for (KeyValue> &kv : physics_quadrant_map) { + Ref &physics_quadrant = kv.value; + for (const KeyValue &kvbody : physics_quadrant->bodies) { + ps->body_set_mode(kvbody.value.body, use_kinematic_bodies ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + } } } } // ----------- // Mark the physics state as up to date. - _physics_was_cleaned_up = forced_cleanup; + _physics_was_cleaned_up = forced_cleanup || !occlusion_enabled; +} + +void TileMapLayer::_physics_quadrants_update_cell(CellData &r_cell_data, SelfList::List &r_dirty_physics_quadrant_list) { + // Check if the cell is valid and retrieve its y_sort_origin. + bool is_valid = false; + TileSetSource *source; + if (tile_set->has_source(r_cell_data.cell.source_id)) { + source = *tile_set->get_source(r_cell_data.cell.source_id); + TileSetAtlasSource *atlas_source = Object::cast_to(source); + if (atlas_source && atlas_source->has_tile(r_cell_data.cell.get_atlas_coords()) && atlas_source->has_alternative_tile(r_cell_data.cell.get_atlas_coords(), r_cell_data.cell.alternative_tile)) { + is_valid = true; + } + } + + if (is_valid) { + // Get the quadrant coords. + const Vector2i &coords = r_cell_data.coords; + Vector2i quadrant_coords = _coords_to_quadrant_coords(coords, physics_quadrant_size); + + Ref physics_quadrant; + if (physics_quadrant_map.has(quadrant_coords)) { + // Reuse existing physics quadrant. + physics_quadrant = physics_quadrant_map[quadrant_coords]; + } else { + // Create a new physics quadrant. + physics_quadrant.instantiate(); + physics_quadrant->quadrant_coords = quadrant_coords; + physics_quadrant_map[quadrant_coords] = physics_quadrant; + } + + // Mark the old quadrant as dirty (if it exists). + if (r_cell_data.physics_quadrant.is_valid()) { + if (!r_cell_data.physics_quadrant->dirty_quadrant_list_element.in_list()) { + r_dirty_physics_quadrant_list.add(&r_cell_data.physics_quadrant->dirty_quadrant_list_element); + } + } + + // Remove the cell from that quadrant. + if (r_cell_data.physics_quadrant_list_element.in_list()) { + r_cell_data.physics_quadrant_list_element.remove_from_list(); + } + + // Add the cell to its new quadrant. + r_cell_data.physics_quadrant = physics_quadrant; + r_cell_data.physics_quadrant->cells.add(&r_cell_data.physics_quadrant_list_element); + + // Add the new quadrant to the dirty quadrant list. + if (!physics_quadrant->dirty_quadrant_list_element.in_list()) { + r_dirty_physics_quadrant_list.add(&physics_quadrant->dirty_quadrant_list_element); + } + } else { + Ref physics_quadrant = r_cell_data.physics_quadrant; + + // Remove the cell from its quadrant. + r_cell_data.physics_quadrant = Ref(); + if (r_cell_data.physics_quadrant_list_element.in_list()) { + physics_quadrant->cells.remove(&r_cell_data.physics_quadrant_list_element); + } + + if (physics_quadrant.is_valid()) { + // Add the quadrant to the dirty quadrant list. + if (!physics_quadrant->dirty_quadrant_list_element.in_list()) { + r_dirty_physics_quadrant_list.add(&physics_quadrant->dirty_quadrant_list_element); + } + } + } } void TileMapLayer::_physics_notification(int p_what) { @@ -737,15 +978,12 @@ void TileMapLayer::_physics_notification(int p_what) { case NOTIFICATION_TRANSFORM_CHANGED: // Move the collisison shapes along with the TileMap. if (is_inside_tree() && tile_set.is_valid()) { - for (KeyValue &kv : tile_map_layer_data) { - const CellData &cell_data = kv.value; - - for (RID body : cell_data.bodies) { - if (body.is_valid()) { - Transform2D xform(0, tile_set->map_to_local(kv.key)); - xform = gl_transform * xform; - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - } + for (KeyValue> &kv : physics_quadrant_map) { + for (const KeyValue &kvbody : kv.value->bodies) { + const RID &body = kvbody.value.body; + Transform2D xform(0, tile_set->map_to_local(kv.key)); + xform = gl_transform * xform; + ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); } } } @@ -755,150 +993,18 @@ void TileMapLayer::_physics_notification(int p_what) { if (is_inside_tree()) { RID space = get_world_2d()->get_space(); - for (KeyValue &kv : tile_map_layer_data) { - const CellData &cell_data = kv.value; - - for (RID body : cell_data.bodies) { - if (body.is_valid()) { - ps->body_set_space(body, space); - } - } - } - } - } -} - -void TileMapLayer::_physics_clear_cell(CellData &r_cell_data) { - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - - // Clear bodies. - for (RID body : r_cell_data.bodies) { - if (body.is_valid()) { - bodies_coords.erase(body); - ps->free(body); - } - } - r_cell_data.bodies.clear(); -} - -void TileMapLayer::_physics_update_cell(CellData &r_cell_data) { - Transform2D gl_transform = get_global_transform(); - RID space = get_world_2d()->get_space(); - PhysicsServer2D *ps = PhysicsServer2D::get_singleton(); - - // Recreate bodies and shapes. - TileMapCell &c = r_cell_data.cell; - - TileSetSource *source; - if (tile_set->has_source(c.source_id)) { - source = *tile_set->get_source(c.source_id); - - if (source->has_tile(c.get_atlas_coords()) && source->has_alternative_tile(c.get_atlas_coords(), c.alternative_tile)) { - TileSetAtlasSource *atlas_source = Object::cast_to(source); - if (atlas_source) { - const TileData *tile_data; - if (r_cell_data.runtime_tile_data_cache) { - tile_data = r_cell_data.runtime_tile_data_cache; - } else { - tile_data = atlas_source->get_tile_data(c.get_atlas_coords(), c.alternative_tile); - } - - // Transform flags. - bool flip_h = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_H); - bool flip_v = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_FLIP_V); - bool transpose = (c.alternative_tile & TileSetAtlasSource::TRANSFORM_TRANSPOSE); - - // Free unused bodies then resize the bodies array. - for (uint32_t i = tile_set->get_physics_layers_count(); i < r_cell_data.bodies.size(); i++) { - RID &body = r_cell_data.bodies[i]; - if (body.is_valid()) { - bodies_coords.erase(body); - ps->free(body); - body = RID(); - } - } - r_cell_data.bodies.resize(tile_set->get_physics_layers_count()); - - for (uint32_t tile_set_physics_layer = 0; tile_set_physics_layer < (uint32_t)tile_set->get_physics_layers_count(); tile_set_physics_layer++) { - Ref physics_material = tile_set->get_physics_layer_physics_material(tile_set_physics_layer); - uint32_t physics_layer = tile_set->get_physics_layer_collision_layer(tile_set_physics_layer); - uint32_t physics_mask = tile_set->get_physics_layer_collision_mask(tile_set_physics_layer); - real_t physics_priority = tile_set->get_physics_layer_collision_priority(tile_set_physics_layer); - - RID body = r_cell_data.bodies[tile_set_physics_layer]; - if (tile_data->get_collision_polygons_count(tile_set_physics_layer) == 0) { - // No body needed, free it if it exists. - if (body.is_valid()) { - bodies_coords.erase(body); - ps->free(body); - } - body = RID(); - } else { - // Create or update the body. - if (!body.is_valid()) { - body = ps->body_create(); - } - bodies_coords[body] = r_cell_data.coords; - ps->body_set_mode(body, use_kinematic_bodies ? PhysicsServer2D::BODY_MODE_KINEMATIC : PhysicsServer2D::BODY_MODE_STATIC); + for (KeyValue> &kv : physics_quadrant_map) { + for (const KeyValue &kvbody : kv.value->bodies) { + const RID &body = kvbody.value.body; ps->body_set_space(body, space); - - Transform2D xform; - xform.set_origin(tile_set->map_to_local(r_cell_data.coords)); - xform = gl_transform * xform; - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM, xform); - - ps->body_attach_object_instance_id(body, tile_map_node ? tile_map_node->get_instance_id() : get_instance_id()); - ps->body_set_collision_layer(body, physics_layer); - ps->body_set_collision_mask(body, physics_mask); - ps->body_set_collision_priority(body, physics_priority); - ps->body_set_pickable(body, false); - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_LINEAR_VELOCITY, tile_data->get_constant_linear_velocity(tile_set_physics_layer)); - ps->body_set_state(body, PhysicsServer2D::BODY_STATE_ANGULAR_VELOCITY, tile_data->get_constant_angular_velocity(tile_set_physics_layer)); - - if (physics_material.is_null()) { - ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, 0); - ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, 1); - } else { - ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_BOUNCE, physics_material->computed_bounce()); - ps->body_set_param(body, PhysicsServer2D::BODY_PARAM_FRICTION, physics_material->computed_friction()); - } - - // Clear body's shape if needed. - ps->body_clear_shapes(body); - - // Add the shapes to the body. - int body_shape_index = 0; - for (int polygon_index = 0; polygon_index < tile_data->get_collision_polygons_count(tile_set_physics_layer); polygon_index++) { - // Iterate over the polygons. - bool one_way_collision = tile_data->is_collision_polygon_one_way(tile_set_physics_layer, polygon_index); - float one_way_collision_margin = tile_data->get_collision_polygon_one_way_margin(tile_set_physics_layer, polygon_index); - int shapes_count = tile_data->get_collision_polygon_shapes_count(tile_set_physics_layer, polygon_index); - for (int shape_index = 0; shape_index < shapes_count; shape_index++) { - // Add decomposed convex shapes. - Ref shape = tile_data->get_collision_polygon_shape(tile_set_physics_layer, polygon_index, shape_index, flip_h, flip_v, transpose); - ps->body_add_shape(body, shape->get_rid()); - ps->body_set_shape_as_one_way_collision(body, body_shape_index, one_way_collision, one_way_collision_margin); - - body_shape_index++; - } - } } - - // Set the body again. - r_cell_data.bodies[tile_set_physics_layer] = body; } - - return; } - } } - - // If we did not return earlier, clear the cell. - _physics_clear_cell(r_cell_data); } #ifdef DEBUG_ENABLED -void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data) { +void TileMapLayer::_physics_draw_quadrant_debug(const RID &p_canvas_item, DebugQuadrant &r_debug_quadrant) { // Draw the debug collision shapes. ERR_FAIL_COND(tile_set.is_null()); @@ -929,25 +1035,97 @@ void TileMapLayer::_physics_draw_cell_debug(const RID &p_canvas_item, const Vect Vector color; color.push_back(debug_collision_color); - Transform2D quadrant_to_local(0, p_quadrant_pos); - Transform2D global_to_quadrant = (get_global_transform() * quadrant_to_local).affine_inverse(); + RandomPCG rand; + rand.seed(hash_murmur3_one_real(r_debug_quadrant.quadrant_coords.y, hash_murmur3_one_real(r_debug_quadrant.quadrant_coords.x))); - for (RID body : r_cell_data.bodies) { - if (body.is_valid()) { - Transform2D body_to_quadrant = global_to_quadrant * Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)); - rs->canvas_item_add_set_transform(p_canvas_item, body_to_quadrant); - for (int shape_index = 0; shape_index < ps->body_get_shape_count(body); shape_index++) { - const RID &shape = ps->body_get_shape(body, shape_index); - const PhysicsServer2D::ShapeType &type = ps->shape_get_type(shape); - if (type == PhysicsServer2D::SHAPE_CONVEX_POLYGON) { - rs->canvas_item_add_polygon(p_canvas_item, ps->shape_get_data(shape), color); - } else { - WARN_PRINT("Wrong shape type for a tile, should be SHAPE_CONVEX_POLYGON."); + // Create a mesh. + if (!r_debug_quadrant.physics_mesh.is_valid()) { + r_debug_quadrant.physics_mesh = rs->mesh_create(); + } + rs->mesh_clear(r_debug_quadrant.physics_mesh); + + // Redraw all shapes from the physics quadrants + Rect2i covered_cell_area = Rect2i(r_debug_quadrant.quadrant_coords * TILE_MAP_DEBUG_QUADRANT_SIZE, Vector2i(TILE_MAP_DEBUG_QUADRANT_SIZE, TILE_MAP_DEBUG_QUADRANT_SIZE)); + Vector2i first_physics_quadrant_coords = _coords_to_quadrant_coords(covered_cell_area.get_position() - Vector2i(1, 1), physics_quadrant_size) + Vector2i(1, 1); + Vector2i last_physics_quadrant_coords = _coords_to_quadrant_coords(covered_cell_area.get_end() - Vector2i(1, 1), physics_quadrant_size) + Vector2i(1, 1); + + // Arrays to generate a mesh. + for (int x = first_physics_quadrant_coords.x; x < last_physics_quadrant_coords.x; x++) { + for (int y = first_physics_quadrant_coords.y; y < last_physics_quadrant_coords.y; y++) { + const Vector2i physics_quadrant_coords = Vector2i(x, y); + if (!physics_quadrant_map.has(physics_quadrant_coords)) { + continue; + } + r_debug_quadrant.drawn_to = true; + Ref physics_quadrant = physics_quadrant_map[physics_quadrant_coords]; + + const Vector2 debug_quadrant_pos = tile_set->map_to_local(r_debug_quadrant.quadrant_coords * TILE_MAP_DEBUG_QUADRANT_SIZE); + Transform2D global_to_debug_quadrant = (get_global_transform() * Transform2D(0, debug_quadrant_pos)).affine_inverse(); + + for (const KeyValue &kvbody : physics_quadrant->bodies) { + const RID &body = kvbody.value.body; + Transform2D body_to_quadrant = global_to_debug_quadrant * Transform2D(ps->body_get_state(body, PhysicsServer2D::BODY_STATE_TRANSFORM)); + + Color random_variation_color; + random_variation_color.set_hsv( + debug_collision_color.get_h() + rand.random(-1.0, 1.0) * 0.05, + debug_collision_color.get_s(), + debug_collision_color.get_v() + rand.random(-1.0, 1.0) * 0.1, + debug_collision_color.a); + + int shape_count = ps->body_get_shape_count(body); + for (int shape_index = 0; shape_index < shape_count; shape_index++) { + const RID &shape = ps->body_get_shape(body, shape_index); + const Transform2D &shape_xform = ps->body_get_shape_transform(body, shape_index); + const PhysicsServer2D::ShapeType &type = ps->shape_get_type(shape); + + if (type == PhysicsServer2D::SHAPE_CONVEX_POLYGON) { + PackedVector2Array outline = ps->shape_get_data(shape); + + PackedVector2Array vertex_array; + vertex_array.resize(outline.size() + 1); + + PackedColorArray face_color_array; + face_color_array.resize(outline.size() + 1); + PackedInt32Array face_index_array; + face_index_array.resize((outline.size() - 2) * 3); + + PackedColorArray line_color_array; + line_color_array.resize(outline.size() + 1); + + for (int i = 0; i < outline.size() + 1; i++) { + Vector2 vertex = (body_to_quadrant * shape_xform).xform(outline[i % outline.size()]); + vertex_array.write[i] = vertex; + face_color_array.write[i] = random_variation_color; + line_color_array.write[i] = random_variation_color.lightened(0.2); + } + for (int i = 0; i < outline.size() - 2; i++) { + face_index_array.write[i * 3] = 0; + face_index_array.write[i * 3 + 1] = i + 1; + face_index_array.write[i * 3 + 2] = i + 2; + } + + Array face_mesh_array; + face_mesh_array.resize(Mesh::ARRAY_MAX); + face_mesh_array[Mesh::ARRAY_VERTEX] = vertex_array; + face_mesh_array[Mesh::ARRAY_INDEX] = face_index_array; + face_mesh_array[Mesh::ARRAY_COLOR] = face_color_array; + rs->mesh_add_surface_from_arrays(r_debug_quadrant.physics_mesh, RS::PRIMITIVE_TRIANGLES, face_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + + Array line_mesh_array; + line_mesh_array.resize(Mesh::ARRAY_MAX); + line_mesh_array[Mesh::ARRAY_VERTEX] = vertex_array; + line_mesh_array[Mesh::ARRAY_COLOR] = line_color_array; + + rs->mesh_add_surface_from_arrays(r_debug_quadrant.physics_mesh, RS::PRIMITIVE_LINE_STRIP, line_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES); + } else { + WARN_PRINT("Wrong shape type for a tile, should be SHAPE_CONVEX_POLYGON."); + } } } - rs->canvas_item_add_set_transform(p_canvas_item, Transform2D()); } } + rs->canvas_item_add_mesh(p_canvas_item, r_debug_quadrant.physics_mesh, Transform2D()); } #endif // DEBUG_ENABLED @@ -1870,6 +2048,8 @@ void TileMapLayer::_bind_methods() { ClassDB::bind_method(D_METHOD("is_using_kinematic_bodies"), &TileMapLayer::is_using_kinematic_bodies); ClassDB::bind_method(D_METHOD("set_collision_visibility_mode", "visibility_mode"), &TileMapLayer::set_collision_visibility_mode); ClassDB::bind_method(D_METHOD("get_collision_visibility_mode"), &TileMapLayer::get_collision_visibility_mode); + ClassDB::bind_method(D_METHOD("set_physics_quadrant_size", "size"), &TileMapLayer::set_physics_quadrant_size); + ClassDB::bind_method(D_METHOD("get_physics_quadrant_size"), &TileMapLayer::get_physics_quadrant_size); ClassDB::bind_method(D_METHOD("set_occlusion_enabled", "enabled"), &TileMapLayer::set_occlusion_enabled); ClassDB::bind_method(D_METHOD("is_occlusion_enabled"), &TileMapLayer::is_occlusion_enabled); @@ -1898,6 +2078,7 @@ void TileMapLayer::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::BOOL, "collision_enabled"), "set_collision_enabled", "is_collision_enabled"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_kinematic_bodies"), "set_use_kinematic_bodies", "is_using_kinematic_bodies"); ADD_PROPERTY(PropertyInfo(Variant::INT, "collision_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_collision_visibility_mode", "get_collision_visibility_mode"); + ADD_PROPERTY(PropertyInfo(Variant::INT, "physics_quadrant_size"), "set_physics_quadrant_size", "get_physics_quadrant_size"); ADD_GROUP("Navigation", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "navigation_enabled"), "set_navigation_enabled", "is_navigation_enabled"); ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_visibility_mode", PROPERTY_HINT_ENUM, "Default,Force Show,Force Hide"), "set_navigation_visibility_mode", "get_navigation_visibility_mode"); @@ -3008,6 +3189,22 @@ TileMapLayer::DebugVisibilityMode TileMapLayer::get_collision_visibility_mode() return collision_visibility_mode; } +void TileMapLayer::set_physics_quadrant_size(int p_size) { + if (physics_quadrant_size == p_size) { + return; + } + ERR_FAIL_COND_MSG(p_size < 1, "Physics quandrant size cannot be smaller than 1."); + physics_quadrant_size = p_size; + + dirty.flags[DIRTY_FLAGS_LAYER_PHYSICS_QUADRANT_SIZE] = true; + _queue_internal_update(); + emit_signal(CoreStringName(changed)); +} + +int TileMapLayer::get_physics_quadrant_size() const { + return physics_quadrant_size; +} + void TileMapLayer::set_occlusion_enabled(bool p_enabled) { if (occlusion_enabled == p_enabled) { return; diff --git a/scene/2d/tile_map_layer.h b/scene/2d/tile_map_layer.h index e91a558ffa..35728ba6f7 100644 --- a/scene/2d/tile_map_layer.h +++ b/scene/2d/tile_map_layer.h @@ -98,21 +98,20 @@ public: class DebugQuadrant; #endif // DEBUG_ENABLED class RenderingQuadrant; +class PhysicsQuadrant; struct CellData { Vector2i coords; TileMapCell cell; - // Debug. - SelfList debug_quadrant_list_element; - // Rendering. Ref rendering_quadrant; SelfList rendering_quadrant_list_element; LocalVector> occluders; // Physics. - LocalVector bodies; + Ref physics_quadrant; + SelfList physics_quadrant_list_element; // Navigation. LocalVector navigation_regions; @@ -135,28 +134,26 @@ struct CellData { coords = p_other.coords; cell = p_other.cell; occluders = p_other.occluders; - bodies = p_other.bodies; navigation_regions = p_other.navigation_regions; scene = p_other.scene; runtime_tile_data_cache = p_other.runtime_tile_data_cache; } CellData(const CellData &p_other) : - debug_quadrant_list_element(this), rendering_quadrant_list_element(this), + physics_quadrant_list_element(this), dirty_list_element(this) { coords = p_other.coords; cell = p_other.cell; occluders = p_other.occluders; - bodies = p_other.bodies; navigation_regions = p_other.navigation_regions; scene = p_other.scene; runtime_tile_data_cache = p_other.runtime_tile_data_cache; } CellData() : - debug_quadrant_list_element(this), rendering_quadrant_list_element(this), + physics_quadrant_list_element(this), dirty_list_element(this) { } }; @@ -177,6 +174,11 @@ public: SelfList::List cells; RID canvas_item; + RID physics_mesh; + + // Used to deleted unused quadrants. + bool drawn_to = false; + SelfList dirty_quadrant_list_element; DebugQuadrant() : @@ -220,6 +222,88 @@ public: } }; +class PhysicsQuadrant : public RefCounted { + GDCLASS(PhysicsQuadrant, RefCounted); + +public: + struct PhysicsBodyKey { + int physics_layer = 0; + Vector2 linear_velocity; + real_t angular_velocity = 0.0; + bool one_way_collision = false; + real_t one_way_collision_margin = 0.0; + + bool operator<(const PhysicsBodyKey &p_other) const { + if (physics_layer == p_other.physics_layer) { + if (linear_velocity == p_other.linear_velocity) { + if (angular_velocity == p_other.angular_velocity) { + if (one_way_collision == p_other.one_way_collision) { + return one_way_collision_margin < p_other.one_way_collision_margin; + } + return one_way_collision < p_other.one_way_collision; + } + return angular_velocity < p_other.angular_velocity; + } + return linear_velocity < p_other.linear_velocity; + } + return physics_layer < p_other.physics_layer; + } + + bool operator!=(const PhysicsBodyKey &p_other) const { + return !this->operator==(p_other); + } + bool operator==(const PhysicsBodyKey &p_other) const { + return physics_layer == p_other.physics_layer && + linear_velocity == p_other.linear_velocity && + angular_velocity == p_other.angular_velocity && + one_way_collision == p_other.one_way_collision && + one_way_collision_margin == p_other.one_way_collision_margin; + } + }; + + struct PhysicsBodyKeyHasher { + static uint32_t hash(const PhysicsBodyKey &p_hash) { + uint32_t h = hash_murmur3_one_32(p_hash.physics_layer); + h = hash_murmur3_one_real(p_hash.linear_velocity.x); + h = hash_murmur3_one_real(p_hash.linear_velocity.y, h); + h = hash_murmur3_one_real(p_hash.angular_velocity, h); + return h; + } + }; + + struct PhysicsBodyValue { + RID body; + Vector> polygons; + }; + + struct CoordsWorldComparator { + _ALWAYS_INLINE_ bool operator()(const Vector2 &p_a, const Vector2 &p_b) const { + // We sort the cells by their local coords, as it is needed by rendering. + if (p_a.y == p_b.y) { + return p_a.x > p_b.x; + } else { + return p_a.y < p_b.y; + } + } + }; + + Vector2i quadrant_coords; + SelfList::List cells; + + HashMap bodies; + LocalVector> shapes; + + SelfList dirty_quadrant_list_element; + + PhysicsQuadrant() : + dirty_quadrant_list_element(this) { + } + + ~PhysicsQuadrant() { + cells.clear(); + } +}; + class TileMapLayer : public Node2D { GDCLASS(TileMapLayer, Node2D); @@ -254,6 +338,7 @@ public: DIRTY_FLAGS_LAYER_RENDERING_QUADRANT_SIZE, DIRTY_FLAGS_LAYER_COLLISION_ENABLED, DIRTY_FLAGS_LAYER_USE_KINEMATIC_BODIES, + DIRTY_FLAGS_LAYER_PHYSICS_QUADRANT_SIZE, DIRTY_FLAGS_LAYER_COLLISION_VISIBILITY_MODE, DIRTY_FLAGS_LAYER_OCCLUSION_ENABLED, DIRTY_FLAGS_LAYER_NAVIGATION_ENABLED, @@ -288,6 +373,7 @@ private: bool collision_enabled = true; bool use_kinematic_bodies = false; + int physics_quadrant_size = 16; DebugVisibilityMode collision_visibility_mode = DEBUG_VISIBILITY_MODE_DEFAULT; bool occlusion_enabled = true; @@ -324,13 +410,16 @@ private: void _clear_runtime_update_tile_data_for_cell(CellData &r_cell_data); void _update_cells_callback(bool p_force_cleanup); + // Coords to quadrant coords + Vector2i _coords_to_quadrant_coords(const Vector2i &p_coords, const int p_quadrant_size) const; + // Per-system methods. #ifdef DEBUG_ENABLED HashMap> debug_quadrant_map; - Vector2i _coords_to_debug_quadrant_coords(const Vector2i &p_coords) const; bool _debug_was_cleaned_up = false; void _debug_update(bool p_force_cleanup); - void _debug_quadrants_update_cell(CellData &r_cell_data, SelfList::List &r_dirty_debug_quadrant_list); + void _debug_quadrants_update_cell(CellData &r_cell_data); + void _get_debug_quadrant_for_cell(const Vector2i &p_coords); #endif // DEBUG_ENABLED HashMap> rendering_quadrant_map; @@ -344,14 +433,16 @@ private: void _rendering_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data); #endif // DEBUG_ENABLED + HashMap> physics_quadrant_map; HashMap bodies_coords; // Mapping for RID to coords. bool _physics_was_cleaned_up = false; void _physics_update(bool p_force_cleanup); void _physics_notification(int p_what); + void _physics_quadrants_update_cell(CellData &r_cell_data, SelfList::List &r_dirty_physics_quadrant_list); void _physics_clear_cell(CellData &r_cell_data); void _physics_update_cell(CellData &r_cell_data); #ifdef DEBUG_ENABLED - void _physics_draw_cell_debug(const RID &p_canvas_item, const Vector2 &p_quadrant_pos, const CellData &r_cell_data); + void _physics_draw_quadrant_debug(const RID &p_canvas_item, DebugQuadrant &r_debug_quadrant); #endif // DEBUG_ENABLED bool _navigation_was_cleaned_up = false; @@ -502,6 +593,8 @@ public: bool is_using_kinematic_bodies() const; void set_collision_visibility_mode(DebugVisibilityMode p_show_collision); DebugVisibilityMode get_collision_visibility_mode() const; + void set_physics_quadrant_size(int p_size); + int get_physics_quadrant_size() const; void set_occlusion_enabled(bool p_enabled); bool is_occlusion_enabled() const;