diff options
32 files changed, 741 insertions, 1245 deletions
diff --git a/core/object/message_queue.cpp b/core/object/message_queue.cpp index 83a19554dc..90536e58ff 100644 --- a/core/object/message_queue.cpp +++ b/core/object/message_queue.cpp @@ -183,7 +183,7 @@ Error CallQueue::push_notification(ObjectID p_id, int p_notification) { if ((page_bytes[pages_used - 1] + room_needed) > uint32_t(PAGE_SIZE_BYTES)) { if (pages_used == max_pages) { - fprintf(stderr, "Failed notification: %s target ID: %s. Message queue out of memory. %s\n", itos(p_notification).utf8().get_data(), itos(p_id).utf8().get_data(), error_text.utf8().get_data()); + fprintf(stderr, "Failed notification: %d target ID: %s. Message queue out of memory. %s\n", p_notification, itos(p_id).utf8().get_data(), error_text.utf8().get_data()); statistics(); UNLOCK_MUTEX; return ERR_OUT_OF_MEMORY; @@ -256,7 +256,7 @@ Error CallQueue::_transfer_messages_to_main_queue() { // Any other possibly existing source page needs to be added. if (mq->pages_used + (pages_used - src_page) > mq->max_pages) { - ERR_PRINT("Failed appending thread queue. Message queue out of memory. " + mq->error_text); + fprintf(stderr, "Failed appending thread queue. Message queue out of memory. %s\n", mq->error_text.utf8().get_data()); mq->statistics(); mq->mutex.unlock(); return ERR_OUT_OF_MEMORY; @@ -462,8 +462,8 @@ void CallQueue::statistics() { } break; } if (null_target) { - //object was deleted - print_line("Object was deleted while awaiting a callback"); + // Object was deleted. + fprintf(stdout, "Object was deleted while awaiting a callback.\n"); null_count++; } @@ -481,19 +481,19 @@ void CallQueue::statistics() { } } - print_line("TOTAL PAGES: " + itos(pages_used) + " (" + itos(pages_used * PAGE_SIZE_BYTES) + " bytes)."); - print_line("NULL count: " + itos(null_count)); + fprintf(stdout, "TOTAL PAGES: %d (%d bytes).\n", pages_used, pages_used * PAGE_SIZE_BYTES); + fprintf(stdout, "NULL count: %d.\n", null_count); for (const KeyValue<StringName, int> &E : set_count) { - print_line("SET " + E.key + ": " + itos(E.value)); + fprintf(stdout, "SET %s: %d.\n", String(E.key).utf8().get_data(), E.value); } for (const KeyValue<Callable, int> &E : call_count) { - print_line("CALL " + E.key + ": " + itos(E.value)); + fprintf(stdout, "CALL %s: %d.\n", String(E.key).utf8().get_data(), E.value); } for (const KeyValue<int, int> &E : notify_count) { - print_line("NOTIFY " + itos(E.key) + ": " + itos(E.value)); + fprintf(stdout, "NOTIFY %d: %d.\n", E.key, E.value); } UNLOCK_MUTEX; diff --git a/doc/classes/InputEventScreenDrag.xml b/doc/classes/InputEventScreenDrag.xml index bd6c26f561..e77040204c 100644 --- a/doc/classes/InputEventScreenDrag.xml +++ b/doc/classes/InputEventScreenDrag.xml @@ -17,7 +17,7 @@ Returns [code]true[/code] when using the eraser end of a stylus pen. </member> <member name="position" type="Vector2" setter="set_position" getter="get_position" default="Vector2(0, 0)"> - The drag position. + The drag position in the viewport the node is in, using the coordinate system of this viewport. </member> <member name="pressure" type="float" setter="set_pressure" getter="get_pressure" default="0.0"> Represents the pressure the user puts on the pen. Ranges from [code]0.0[/code] to [code]1.0[/code]. diff --git a/doc/classes/InputEventScreenTouch.xml b/doc/classes/InputEventScreenTouch.xml index 9ed6fc191c..a42b122e3b 100644 --- a/doc/classes/InputEventScreenTouch.xml +++ b/doc/classes/InputEventScreenTouch.xml @@ -20,7 +20,7 @@ The touch index in the case of a multi-touch event. One index = one finger. </member> <member name="position" type="Vector2" setter="set_position" getter="get_position" default="Vector2(0, 0)"> - The touch position, in screen (global) coordinates. + The touch position in the viewport the node is in, using the coordinate system of this viewport. </member> <member name="pressed" type="bool" setter="set_pressed" getter="is_pressed" default="false"> If [code]true[/code], the touch's state is pressed. If [code]false[/code], the touch's state is released. diff --git a/doc/classes/NavigationMeshSourceGeometryData2D.xml b/doc/classes/NavigationMeshSourceGeometryData2D.xml index 9c05248eff..609877fadc 100644 --- a/doc/classes/NavigationMeshSourceGeometryData2D.xml +++ b/doc/classes/NavigationMeshSourceGeometryData2D.xml @@ -16,6 +16,14 @@ Adds the outline points of a shape as obstructed area. </description> </method> + <method name="add_projected_obstruction"> + <return type="void" /> + <param index="0" name="vertices" type="PackedVector2Array" /> + <param index="1" name="carve" type="bool" /> + <description> + Adds a projected obstruction shape to the source geometry. If [param carve] is [code]true[/code] the carved shape will not be affected by additional offsets (e.g. agent radius) of the navigation mesh baking process. + </description> + </method> <method name="add_traversable_outline"> <return type="void" /> <param index="0" name="shape_outline" type="PackedVector2Array" /> @@ -29,12 +37,26 @@ Clears the internal data. </description> </method> + <method name="clear_projected_obstructions"> + <return type="void" /> + <description> + Clears all projected obstructions. + </description> + </method> <method name="get_obstruction_outlines" qualifiers="const"> <return type="PackedVector2Array[]" /> <description> Returns all the obstructed area outlines arrays. </description> </method> + <method name="get_projected_obstructions" qualifiers="const"> + <return type="Array" /> + <description> + Returns the projected obstructions as an [Array] of dictionaries. Each [Dictionary] contains the following entries: + - [code]vertices[/code] - A [PackedFloat32Array] that defines the outline points of the projected shape. + - [code]carve[/code] - A [bool] that defines how the projected shape affects the navigation mesh baking. If [code]true[/code] the projected shape will not be affected by addition offsets, e.g. agent radius. + </description> + </method> <method name="get_traversable_outlines" qualifiers="const"> <return type="PackedVector2Array[]" /> <description> @@ -61,6 +83,19 @@ Sets all the obstructed area outlines arrays. </description> </method> + <method name="set_projected_obstructions"> + <return type="void" /> + <param index="0" name="projected_obstructions" type="Array" /> + <description> + Sets the projected obstructions with an Array of Dictionaries with the following key value pairs: + [codeblocks] + [gdscript] + "vertices" : PackedFloat32Array + "carve" : bool + [/gdscript] + [/codeblocks] + </description> + </method> <method name="set_traversable_outlines"> <return type="void" /> <param index="0" name="traversable_outlines" type="PackedVector2Array[]" /> diff --git a/doc/classes/NavigationMeshSourceGeometryData3D.xml b/doc/classes/NavigationMeshSourceGeometryData3D.xml index a3dcd4d209..322e2e7c66 100644 --- a/doc/classes/NavigationMeshSourceGeometryData3D.xml +++ b/doc/classes/NavigationMeshSourceGeometryData3D.xml @@ -33,18 +33,44 @@ Adds an [Array] the size of [constant Mesh.ARRAY_MAX] and with vertices at index [constant Mesh.ARRAY_VERTEX] and indices at index [constant Mesh.ARRAY_INDEX] to the navigation mesh baking data. The array must have valid triangulated mesh data to be considered. Since [NavigationMesh] resources have no transform, all vertex positions need to be offset by the node's transform using [param xform]. </description> </method> + <method name="add_projected_obstruction"> + <return type="void" /> + <param index="0" name="vertices" type="PackedVector3Array" /> + <param index="1" name="elevation" type="float" /> + <param index="2" name="height" type="float" /> + <param index="3" name="carve" type="bool" /> + <description> + Adds a projected obstruction shape to the source geometry. The [param vertices] are considered projected on a xz-axes plane, placed at the global y-axis [param elevation] and extruded by [param height]. If [param carve] is [code]true[/code] the carved shape will not be affected by additional offsets (e.g. agent radius) of the navigation mesh baking process. + </description> + </method> <method name="clear"> <return type="void" /> <description> Clears the internal data. </description> </method> + <method name="clear_projected_obstructions"> + <return type="void" /> + <description> + Clears all projected obstructions. + </description> + </method> <method name="get_indices" qualifiers="const"> <return type="PackedInt32Array" /> <description> Returns the parsed source geometry data indices array. </description> </method> + <method name="get_projected_obstructions" qualifiers="const"> + <return type="Array" /> + <description> + Returns the projected obstructions as an [Array] of dictionaries. Each [Dictionary] contains the following entries: + - [code]vertices[/code] - A [PackedFloat32Array] that defines the outline points of the projected shape. + - [code]elevation[/code] - A [float] that defines the projected shape placement on the y-axis. + - [code]height[/code] - A [float] that defines how much the projected shape is extruded along the y-axis. + - [code]carve[/code] - A [bool] that defines how the obstacle affects the navigation mesh baking. If [code]true[/code] the projected shape will not be affected by addition offsets, e.g. agent radius. + </description> + </method> <method name="get_vertices" qualifiers="const"> <return type="PackedFloat32Array" /> <description> @@ -72,6 +98,21 @@ [b]Warning:[/b] Inappropriate data can crash the baking process of the involved third-party libraries. </description> </method> + <method name="set_projected_obstructions"> + <return type="void" /> + <param index="0" name="projected_obstructions" type="Array" /> + <description> + Sets the projected obstructions with an Array of Dictionaries with the following key value pairs: + [codeblocks] + [gdscript] + "vertices" : PackedFloat32Array + "elevation" : float + "height" : float + "carve" : bool + [/gdscript] + [/codeblocks] + </description> + </method> <method name="set_vertices"> <return type="void" /> <param index="0" name="vertices" type="PackedFloat32Array" /> diff --git a/doc/classes/NavigationObstacle2D.xml b/doc/classes/NavigationObstacle2D.xml index 19b515c7cb..12205e2ac3 100644 --- a/doc/classes/NavigationObstacle2D.xml +++ b/doc/classes/NavigationObstacle2D.xml @@ -1,13 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="NavigationObstacle2D" inherits="Node2D" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - 2D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. + 2D obstacle used to affect navigation mesh baking or constrain velocities of avoidance controlled agents. </brief_description> <description> - 2D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. The obstacle needs a navigation map and outline vertices defined to work correctly. - If the obstacle's vertices are winded in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Outlines must not cross or overlap. - Obstacles are [b]not[/b] a replacement for a (re)baked navigation mesh. Obstacles [b]don't[/b] change the resulting path from the pathfinding, obstacles only affect the navigation avoidance agent movement by altering the suggested velocity of the avoidance agent. - Obstacles using vertices can warp to a new position but should not moved every frame as each move requires a rebuild of the avoidance map. + An obstacle needs a navigation map and outline [member vertices] defined to work correctly. The outlines can not cross or overlap. + Obstacles can be included in the navigation mesh baking process when [member affect_navigation_mesh] is enabled. They do not add walkable geometry, instead their role is to discard other source geometry inside the shape. This can be used to prevent navigation mesh from appearing in unwanted places. If [member carve_navigation_mesh] is enabled the baked shape will not be affected by offsets of the navigation mesh baking, e.g. the agent radius. + With [member avoidance_enabled] the obstacle can constrain the avoidance velocities of avoidance using agents. If the obstacle's vertices are wound in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Obstacles using vertices and avoidance can warp to a new position but should not be moved every single frame as each change requires a rebuild of the avoidance map. </description> <tutorials> <link title="Using NavigationObstacles">$DOCS_URL/tutorials/navigation/navigation_using_navigationobstacles.html</link> @@ -49,12 +48,20 @@ </method> </methods> <members> + <member name="affect_navigation_mesh" type="bool" setter="set_affect_navigation_mesh" getter="get_affect_navigation_mesh" default="false"> + If enabled and parsed in a navigation mesh baking process the obstacle will discard source geometry inside its [member vertices] defined shape. + </member> <member name="avoidance_enabled" type="bool" setter="set_avoidance_enabled" getter="get_avoidance_enabled" default="true"> If [code]true[/code] the obstacle affects avoidance using agents. </member> <member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1"> A bitfield determining the avoidance layers for this obstacle. Agents with a matching bit on the their avoidance mask will avoid this obstacle. </member> + <member name="carve_navigation_mesh" type="bool" setter="set_carve_navigation_mesh" getter="get_carve_navigation_mesh" default="false"> + If enabled the obstacle vertices will carve into the baked navigation mesh with the shape unaffected by additional offsets (e.g. agent radius). + It will still be affected by further postprocessing of the baking process, like edge and polygon simplification. + Requires [member affect_navigation_mesh] to be enabled. + </member> <member name="radius" type="float" setter="set_radius" getter="get_radius" default="0.0"> Sets the avoidance radius for the obstacle. </member> diff --git a/doc/classes/NavigationObstacle3D.xml b/doc/classes/NavigationObstacle3D.xml index 998279b3c4..bc6dbabc0e 100644 --- a/doc/classes/NavigationObstacle3D.xml +++ b/doc/classes/NavigationObstacle3D.xml @@ -1,13 +1,12 @@ <?xml version="1.0" encoding="UTF-8" ?> <class name="NavigationObstacle3D" inherits="Node3D" experimental="" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../class.xsd"> <brief_description> - 3D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. + 3D obstacle used to affect navigation mesh baking or constrain velocities of avoidance controlled agents. </brief_description> <description> - 3D Obstacle used in navigation to constrain avoidance controlled agents outside or inside an area. The obstacle needs a navigation map and outline vertices defined to work correctly. - If the obstacle's vertices are winded in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Outlines must not cross or overlap. - Obstacles are [b]not[/b] a replacement for a (re)baked navigation mesh. Obstacles [b]don't[/b] change the resulting path from the pathfinding, obstacles only affect the navigation avoidance agent movement by altering the suggested velocity of the avoidance agent. - Obstacles using vertices can warp to a new position but should not moved every frame as each move requires a rebuild of the avoidance map. + An obstacle needs a navigation map and outline [member vertices] defined to work correctly. The outlines can not cross or overlap and are restricted to a plane projection. This means the y-axis of the vertices is ignored, instead the obstacle's global y-axis position is used for placement. The projected shape is extruded by the obstacles height along the y-axis. + Obstacles can be included in the navigation mesh baking process when [member affect_navigation_mesh] is enabled. They do not add walkable geometry, instead their role is to discard other source geometry inside the shape. This can be used to prevent navigation mesh from appearing in unwanted places, e.g. inside "solid" geometry or on top of it. If [member carve_navigation_mesh] is enabled the baked shape will not be affected by offsets of the navigation mesh baking, e.g. the agent radius. + With [member avoidance_enabled] the obstacle can constrain the avoidance velocities of avoidance using agents. If the obstacle's vertices are wound in clockwise order, avoidance agents will be pushed in by the obstacle, otherwise, avoidance agents will be pushed out. Obstacles using vertices and avoidance can warp to a new position but should not be moved every single frame as each change requires a rebuild of the avoidance map. </description> <tutorials> <link title="Using NavigationObstacles">$DOCS_URL/tutorials/navigation/navigation_using_navigationobstacles.html</link> @@ -49,12 +48,20 @@ </method> </methods> <members> + <member name="affect_navigation_mesh" type="bool" setter="set_affect_navigation_mesh" getter="get_affect_navigation_mesh" default="false"> + If enabled and parsed in a navigation mesh baking process the obstacle will discard source geometry inside its [member vertices] and [member height] defined shape. + </member> <member name="avoidance_enabled" type="bool" setter="set_avoidance_enabled" getter="get_avoidance_enabled" default="true"> If [code]true[/code] the obstacle affects avoidance using agents. </member> <member name="avoidance_layers" type="int" setter="set_avoidance_layers" getter="get_avoidance_layers" default="1"> A bitfield determining the avoidance layers for this obstacle. Agents with a matching bit on the their avoidance mask will avoid this obstacle. </member> + <member name="carve_navigation_mesh" type="bool" setter="set_carve_navigation_mesh" getter="get_carve_navigation_mesh" default="false"> + If enabled the obstacle vertices will carve into the baked navigation mesh with the shape unaffected by additional offsets (e.g. agent radius). + It will still be affected by further postprocessing of the baking process, like edge and polygon simplification. + Requires [member affect_navigation_mesh] to be enabled. + </member> <member name="height" type="float" setter="set_height" getter="get_height" default="1.0"> Sets the obstacle height used in 2D avoidance. 2D avoidance using agent's ignore obstacles that are below or above them. </member> diff --git a/modules/navigation/2d/nav_mesh_generator_2d.cpp b/modules/navigation/2d/nav_mesh_generator_2d.cpp index 3dbf9f8735..069cc0f4a8 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.cpp +++ b/modules/navigation/2d/nav_mesh_generator_2d.cpp @@ -35,6 +35,7 @@ #include "core/config/project_settings.h" #include "scene/2d/mesh_instance_2d.h" #include "scene/2d/multimesh_instance_2d.h" +#include "scene/2d/navigation_obstacle_2d.h" #include "scene/2d/physics/static_body_2d.h" #include "scene/2d/polygon_2d.h" #include "scene/2d/tile_map.h" @@ -233,6 +234,7 @@ void NavMeshGenerator2D::generator_parse_geometry_node(Ref<NavigationPolygon> p_ 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_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { @@ -660,6 +662,58 @@ void NavMeshGenerator2D::generator_parse_tilemap_node(const Ref<NavigationPolygo } } +void NavMeshGenerator2D::generator_parse_navigationobstacle_node(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node) { + NavigationObstacle2D *obstacle = Object::cast_to<NavigationObstacle2D>(p_node); + if (obstacle == nullptr) { + return; + } + + if (!obstacle->get_affect_navigation_mesh()) { + return; + } + + const Transform2D node_xform = p_source_geometry_data->root_node_transform * Transform2D(0.0, obstacle->get_global_position()); + + const float obstacle_radius = obstacle->get_radius(); + + if (obstacle_radius > 0.0) { + Vector<Vector2> obstruction_circle_vertices; + + // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. + // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck. + // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty. + static const int circle_points = 12; + + obstruction_circle_vertices.resize(circle_points); + Vector2 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw(); + const real_t circle_point_step = Math_TAU / circle_points; + + for (int i = 0; i < circle_points; i++) { + const float angle = i * circle_point_step; + circle_vertices_ptrw[i] = node_xform.xform(Vector2(Math::cos(angle) * obstacle_radius, Math::sin(angle) * obstacle_radius)); + } + + p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_carve_navigation_mesh()); + } + + const Vector<Vector2> &obstacle_vertices = obstacle->get_vertices(); + + if (obstacle_vertices.is_empty()) { + return; + } + + Vector<Vector2> obstruction_shape_vertices; + obstruction_shape_vertices.resize(obstacle_vertices.size()); + + const Vector2 *obstacle_vertices_ptr = obstacle_vertices.ptr(); + Vector2 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw(); + + for (int i = 0; i < obstacle_vertices.size(); i++) { + obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); + } + p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_carve_navigation_mesh()); +} + void NavMeshGenerator2D::generator_parse_source_geometry_data(Ref<NavigationPolygon> p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_root_node) { List<Node *> parse_nodes; @@ -779,6 +833,30 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation obstruction_polygon_paths.push_back(clip_path); } + const Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_get_projected_obstructions(); + + if (!projected_obstructions.is_empty()) { + for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) { + continue; + } + + Path64 clip_path; + clip_path.reserve(projected_obstruction.vertices.size() / 2); + for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { + const Point64 &point = Point64(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + clip_path.push_back(point); + } + if (!IsPositive(clip_path)) { + std::reverse(clip_path.begin(), clip_path.end()); + } + obstruction_polygon_paths.push_back(clip_path); + } + } + Rect2 baking_rect = p_navigation_mesh->get_baking_rect(); if (baking_rect.has_area()) { Vector2 baking_rect_offset = p_navigation_mesh->get_baking_rect_offset(); @@ -809,6 +887,33 @@ void NavMeshGenerator2D::generator_bake_from_source_geometry_data(Ref<Navigation if (agent_radius_offset > 0.0) { path_solution = InflatePaths(path_solution, -agent_radius_offset, JoinType::Miter, EndType::Polygon); } + + if (!projected_obstructions.is_empty()) { + obstruction_polygon_paths.resize(0); + for (const NavigationMeshSourceGeometryData2D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (!projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 2 != 0) { + continue; + } + + Path64 clip_path; + clip_path.reserve(projected_obstruction.vertices.size() / 2); + for (int i = 0; i < projected_obstruction.vertices.size() / 2; i++) { + const Point64 &point = Point64(projected_obstruction.vertices[i * 2], projected_obstruction.vertices[i * 2 + 1]); + clip_path.push_back(point); + } + if (!IsPositive(clip_path)) { + std::reverse(clip_path.begin(), clip_path.end()); + } + obstruction_polygon_paths.push_back(clip_path); + } + if (obstruction_polygon_paths.size() > 0) { + path_solution = Difference(path_solution, obstruction_polygon_paths, FillRule::NonZero); + } + } + //path_solution = RamerDouglasPeucker(path_solution, 0.025); // real_t border_size = p_navigation_mesh->get_border_size(); diff --git a/modules/navigation/2d/nav_mesh_generator_2d.h b/modules/navigation/2d/nav_mesh_generator_2d.h index b606f3f6fc..2567a170ef 100644 --- a/modules/navigation/2d/nav_mesh_generator_2d.h +++ b/modules/navigation/2d/nav_mesh_generator_2d.h @@ -81,6 +81,7 @@ class NavMeshGenerator2D : public Object { 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_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); diff --git a/modules/navigation/3d/nav_mesh_generator_3d.cpp b/modules/navigation/3d/nav_mesh_generator_3d.cpp index e1ed9c51aa..b3391398a6 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.cpp +++ b/modules/navigation/3d/nav_mesh_generator_3d.cpp @@ -37,6 +37,7 @@ #include "core/os/thread.h" #include "scene/3d/mesh_instance_3d.h" #include "scene/3d/multimesh_instance_3d.h" +#include "scene/3d/navigation_obstacle_3d.h" #include "scene/3d/physics/static_body_3d.h" #include "scene/resources/3d/box_shape_3d.h" #include "scene/resources/3d/capsule_shape_3d.h" @@ -251,6 +252,7 @@ void NavMeshGenerator3D::generator_parse_geometry_node(const Ref<NavigationMesh> #ifdef MODULE_GRIDMAP_ENABLED generator_parse_gridmap_node(p_navigation_mesh, p_source_geometry_data, p_node); #endif + generator_parse_navigationobstacle_node(p_navigation_mesh, p_source_geometry_data, p_node); if (p_recurse_children) { for (int i = 0; i < p_node->get_child_count(); i++) { @@ -569,6 +571,59 @@ void NavMeshGenerator3D::generator_parse_gridmap_node(const Ref<NavigationMesh> } #endif // MODULE_GRIDMAP_ENABLED +void NavMeshGenerator3D::generator_parse_navigationobstacle_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node) { + NavigationObstacle3D *obstacle = Object::cast_to<NavigationObstacle3D>(p_node); + if (obstacle == nullptr) { + return; + } + + if (!obstacle->get_affect_navigation_mesh()) { + return; + } + + const Transform3D node_xform = p_source_geometry_data->root_node_transform * Transform3D(Basis(), obstacle->get_global_position()); + + const float obstacle_radius = obstacle->get_radius(); + + if (obstacle_radius > 0.0) { + Vector<Vector3> obstruction_circle_vertices; + + // The point of this is that the moving obstacle can make a simple hole in the navigation mesh and affect the pathfinding. + // Without, navigation paths can go directly through the middle of the obstacle and conflict with the avoidance to get agents stuck. + // No place for excessive "round" detail here. Every additional edge adds a high cost for something that needs to be quick, not pretty. + static const int circle_points = 12; + + obstruction_circle_vertices.resize(circle_points); + Vector3 *circle_vertices_ptrw = obstruction_circle_vertices.ptrw(); + const real_t circle_point_step = Math_TAU / circle_points; + + for (int i = 0; i < circle_points; i++) { + const float angle = i * circle_point_step; + circle_vertices_ptrw[i] = node_xform.xform(Vector3(Math::cos(angle) * obstacle_radius, 0.0, Math::sin(angle) * obstacle_radius)); + } + + p_source_geometry_data->add_projected_obstruction(obstruction_circle_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y - obstacle_radius, obstacle_radius, obstacle->get_carve_navigation_mesh()); + } + + const Vector<Vector3> &obstacle_vertices = obstacle->get_vertices(); + + if (obstacle_vertices.is_empty()) { + return; + } + + Vector<Vector3> obstruction_shape_vertices; + obstruction_shape_vertices.resize(obstacle_vertices.size()); + + const Vector3 *obstacle_vertices_ptr = obstacle_vertices.ptr(); + Vector3 *obstruction_shape_vertices_ptrw = obstruction_shape_vertices.ptrw(); + + for (int i = 0; i < obstacle_vertices.size(); i++) { + obstruction_shape_vertices_ptrw[i] = node_xform.xform(obstacle_vertices_ptr[i]); + obstruction_shape_vertices_ptrw[i].y = 0.0; + } + p_source_geometry_data->add_projected_obstruction(obstruction_shape_vertices, obstacle->get_global_position().y + p_source_geometry_data->root_node_transform.origin.y, obstacle->get_height(), obstacle->get_carve_navigation_mesh()); +} + void NavMeshGenerator3D::generator_parse_source_geometry_data(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_root_node) { List<Node *> parse_nodes; @@ -741,10 +796,46 @@ void NavMeshGenerator3D::generator_bake_from_source_geometry_data(Ref<Navigation rcFreeHeightField(hf); hf = nullptr; + const Vector<NavigationMeshSourceGeometryData3D::ProjectedObstruction> &projected_obstructions = p_source_geometry_data->_get_projected_obstructions(); + + // Add obstacles to the source geometry. Those will be affected by e.g. agent_radius. + if (!projected_obstructions.is_empty()) { + for (const NavigationMeshSourceGeometryData3D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 3 != 0) { + continue; + } + + const float *projected_obstruction_verts = projected_obstruction.vertices.ptr(); + const int projected_obstruction_nverts = projected_obstruction.vertices.size() / 3; + + rcMarkConvexPolyArea(&ctx, projected_obstruction_verts, projected_obstruction_nverts, projected_obstruction.elevation, projected_obstruction.elevation + projected_obstruction.height, RC_NULL_AREA, *chf); + } + } + bake_state = "Eroding walkable area..."; // step #6 ERR_FAIL_COND(!rcErodeWalkableArea(&ctx, cfg.walkableRadius, *chf)); + // Carve obstacles to the eroded geometry. Those will NOT be affected by e.g. agent_radius because that step is already done. + if (!projected_obstructions.is_empty()) { + for (const NavigationMeshSourceGeometryData3D::ProjectedObstruction &projected_obstruction : projected_obstructions) { + if (!projected_obstruction.carve) { + continue; + } + if (projected_obstruction.vertices.is_empty() || projected_obstruction.vertices.size() % 3 != 0) { + continue; + } + + const float *projected_obstruction_verts = projected_obstruction.vertices.ptr(); + const int projected_obstruction_nverts = projected_obstruction.vertices.size() / 3; + + rcMarkConvexPolyArea(&ctx, projected_obstruction_verts, projected_obstruction_nverts, projected_obstruction.elevation, projected_obstruction.elevation + projected_obstruction.height, RC_NULL_AREA, *chf); + } + } + bake_state = "Partitioning..."; // step #7 if (p_navigation_mesh->get_sample_partition_type() == NavigationMesh::SAMPLE_PARTITION_WATERSHED) { diff --git a/modules/navigation/3d/nav_mesh_generator_3d.h b/modules/navigation/3d/nav_mesh_generator_3d.h index 0251b02235..9c9b3bdefe 100644 --- a/modules/navigation/3d/nav_mesh_generator_3d.h +++ b/modules/navigation/3d/nav_mesh_generator_3d.h @@ -86,6 +86,7 @@ class NavMeshGenerator3D : public Object { #ifdef MODULE_GRIDMAP_ENABLED static void generator_parse_gridmap_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node); #endif // MODULE_GRIDMAP_ENABLED + static void generator_parse_navigationobstacle_node(const Ref<NavigationMesh> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData3D> p_source_geometry_data, Node *p_node); static bool generator_emit_callback(const Callable &p_callback); diff --git a/modules/regex/SCsub b/modules/regex/SCsub index 3e31e3c2d9..f5e2dd5dfc 100644 --- a/modules/regex/SCsub +++ b/modules/regex/SCsub @@ -29,7 +29,7 @@ if env["builtin_pcre2"]: "pcre2_extuni.c", "pcre2_find_bracket.c", "pcre2_jit_compile.c", - # "pcre2_jit_match.c", "pcre2_jit_misc.c", # these files are included in pcre2_jit_compile.c. + # "pcre2_jit_match.c", "pcre2_jit_misc.c", # Included in `pcre2_jit_compile.c`. "pcre2_maketables.c", "pcre2_match.c", "pcre2_match_data.c", @@ -44,6 +44,7 @@ if env["builtin_pcre2"]: "pcre2_substring.c", "pcre2_tables.c", "pcre2_ucd.c", + # "pcre2_ucptables.c", # Included in `pcre2_tables.c`. "pcre2_valid_utf.c", "pcre2_xclass.c", ] diff --git a/platform/ios/detect.py b/platform/ios/detect.py index 4d6e3ae9ba..cd303295ad 100644 --- a/platform/ios/detect.py +++ b/platform/ios/detect.py @@ -52,6 +52,7 @@ def get_flags(): ("target", "template_debug"), ("use_volk", False), ("supported", ["mono"]), + ("builtin_pcre2_with_jit", False), ] diff --git a/platform/macos/native_menu_macos.mm b/platform/macos/native_menu_macos.mm index f00527767c..250b64dc04 100644 --- a/platform/macos/native_menu_macos.mm +++ b/platform/macos/native_menu_macos.mm @@ -271,7 +271,7 @@ void NativeMenuMacOS::set_interface_direction(const RID &p_rid, bool p_is_rtl) { MenuData *md = menus.get_or_null(p_rid); ERR_FAIL_NULL(md); - md->menu.userInterfaceLayoutDirection = p_is_rtl ? NSUserInterfaceLayoutDirectionLeftToRight : NSUserInterfaceLayoutDirectionRightToLeft; + md->menu.userInterfaceLayoutDirection = p_is_rtl ? NSUserInterfaceLayoutDirectionRightToLeft : NSUserInterfaceLayoutDirectionLeftToRight; } void NativeMenuMacOS::set_popup_open_callback(const RID &p_rid, const Callable &p_callback) { diff --git a/platform/windows/native_menu_windows.cpp b/platform/windows/native_menu_windows.cpp index eea30cab9a..84e3611e91 100644 --- a/platform/windows/native_menu_windows.cpp +++ b/platform/windows/native_menu_windows.cpp @@ -289,7 +289,7 @@ int NativeMenuWindows::add_item(const RID &p_rid, const String &p_label, const C item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA; item.fType = MFT_STRING; item.dwItemData = (ULONG_PTR)item_data; - item.dwTypeData = (LPWSTR)label.ptrw(); + item.dwTypeData = label.ptrw() ? (LPWSTR)label.ptrw() : L""; if (!InsertMenuItemW(md->menu, p_index, true, &item)) { memdelete(item_data); @@ -949,7 +949,7 @@ void NativeMenuWindows::set_item_text(const RID &p_rid, int p_idx, const String item.cbSize = sizeof(item); item.fMask = MIIM_FTYPE | MIIM_STRING | MIIM_DATA; if (GetMenuItemInfoW(md->menu, p_idx, true, &item)) { - item.dwTypeData = (LPWSTR)label.ptrw(); + item.dwTypeData = label.ptrw() ? (LPWSTR)label.ptrw() : L""; SetMenuItemInfoW(md->menu, p_idx, true, &item); } } diff --git a/scene/2d/navigation_obstacle_2d.cpp b/scene/2d/navigation_obstacle_2d.cpp index 07a3910720..3bf90249f8 100644 --- a/scene/2d/navigation_obstacle_2d.cpp +++ b/scene/2d/navigation_obstacle_2d.cpp @@ -55,14 +55,24 @@ void NavigationObstacle2D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_avoidance_layers", "layers"), &NavigationObstacle2D::set_avoidance_layers); ClassDB::bind_method(D_METHOD("get_avoidance_layers"), &NavigationObstacle2D::get_avoidance_layers); + ClassDB::bind_method(D_METHOD("set_avoidance_layer_value", "layer_number", "value"), &NavigationObstacle2D::set_avoidance_layer_value); ClassDB::bind_method(D_METHOD("get_avoidance_layer_value", "layer_number"), &NavigationObstacle2D::get_avoidance_layer_value); + ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle2D::set_affect_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle2D::get_affect_navigation_mesh); + + ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle2D::set_carve_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle2D::get_carve_navigation_mesh); + + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius"); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices"); + ADD_GROUP("NavigationMesh", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh"); ADD_GROUP("Avoidance", ""); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); - ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,500,0.01,suffix:px"), "set_radius", "get_radius"); - ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR2_ARRAY, "vertices"), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); } @@ -277,6 +287,22 @@ void NavigationObstacle2D::set_velocity(const Vector2 p_velocity) { velocity_submitted = true; } +void NavigationObstacle2D::set_affect_navigation_mesh(bool p_enabled) { + affect_navigation_mesh = p_enabled; +} + +bool NavigationObstacle2D::get_affect_navigation_mesh() const { + return affect_navigation_mesh; +} + +void NavigationObstacle2D::set_carve_navigation_mesh(bool p_enabled) { + carve_navigation_mesh = p_enabled; +} + +bool NavigationObstacle2D::get_carve_navigation_mesh() const { + return carve_navigation_mesh; +} + void NavigationObstacle2D::_update_map(RID p_map) { map_current = p_map; NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map); diff --git a/scene/2d/navigation_obstacle_2d.h b/scene/2d/navigation_obstacle_2d.h index f9d0e27714..30328f7086 100644 --- a/scene/2d/navigation_obstacle_2d.h +++ b/scene/2d/navigation_obstacle_2d.h @@ -54,6 +54,9 @@ class NavigationObstacle2D : public Node2D { Vector2 previous_velocity; bool velocity_submitted = false; + bool affect_navigation_mesh = false; + bool carve_navigation_mesh = false; + #ifdef DEBUG_ENABLED private: RID debug_canvas_item; @@ -97,6 +100,12 @@ public: void _avoidance_done(Vector3 p_new_velocity); // Dummy + void set_affect_navigation_mesh(bool p_enabled); + bool get_affect_navigation_mesh() const; + + void set_carve_navigation_mesh(bool p_enabled); + bool get_carve_navigation_mesh() const; + private: void _update_map(RID p_map); void _update_position(const Vector2 p_position); diff --git a/scene/3d/navigation_obstacle_3d.cpp b/scene/3d/navigation_obstacle_3d.cpp index 96f7fb137c..f2ac8f789c 100644 --- a/scene/3d/navigation_obstacle_3d.cpp +++ b/scene/3d/navigation_obstacle_3d.cpp @@ -63,12 +63,21 @@ void NavigationObstacle3D::_bind_methods() { ClassDB::bind_method(D_METHOD("set_use_3d_avoidance", "enabled"), &NavigationObstacle3D::set_use_3d_avoidance); ClassDB::bind_method(D_METHOD("get_use_3d_avoidance"), &NavigationObstacle3D::get_use_3d_avoidance); - ADD_GROUP("Avoidance", ""); - ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); - ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); + ClassDB::bind_method(D_METHOD("set_affect_navigation_mesh", "enabled"), &NavigationObstacle3D::set_affect_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_affect_navigation_mesh"), &NavigationObstacle3D::get_affect_navigation_mesh); + + ClassDB::bind_method(D_METHOD("set_carve_navigation_mesh", "enabled"), &NavigationObstacle3D::set_carve_navigation_mesh); + ClassDB::bind_method(D_METHOD("get_carve_navigation_mesh"), &NavigationObstacle3D::get_carve_navigation_mesh); + ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "radius", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_radius", "get_radius"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "height", PROPERTY_HINT_RANGE, "0.0,100,0.01,suffix:m"), "set_height", "get_height"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices"), "set_vertices", "get_vertices"); + ADD_GROUP("NavigationMesh", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "affect_navigation_mesh"), "set_affect_navigation_mesh", "get_affect_navigation_mesh"); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "carve_navigation_mesh"), "set_carve_navigation_mesh", "get_carve_navigation_mesh"); + ADD_GROUP("Avoidance", ""); + ADD_PROPERTY(PropertyInfo(Variant::BOOL, "avoidance_enabled"), "set_avoidance_enabled", "get_avoidance_enabled"); + ADD_PROPERTY(PropertyInfo(Variant::VECTOR3, "velocity", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR), "set_velocity", "get_velocity"); ADD_PROPERTY(PropertyInfo(Variant::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers"); ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_3d_avoidance"), "set_use_3d_avoidance", "get_use_3d_avoidance"); } @@ -321,6 +330,22 @@ void NavigationObstacle3D::set_velocity(const Vector3 p_velocity) { velocity_submitted = true; } +void NavigationObstacle3D::set_affect_navigation_mesh(bool p_enabled) { + affect_navigation_mesh = p_enabled; +} + +bool NavigationObstacle3D::get_affect_navigation_mesh() const { + return affect_navigation_mesh; +} + +void NavigationObstacle3D::set_carve_navigation_mesh(bool p_enabled) { + carve_navigation_mesh = p_enabled; +} + +bool NavigationObstacle3D::get_carve_navigation_mesh() const { + return carve_navigation_mesh; +} + void NavigationObstacle3D::_update_map(RID p_map) { NavigationServer3D::get_singleton()->obstacle_set_map(obstacle, p_map); map_current = p_map; diff --git a/scene/3d/navigation_obstacle_3d.h b/scene/3d/navigation_obstacle_3d.h index 51a84c9a54..e9a4669fa2 100644 --- a/scene/3d/navigation_obstacle_3d.h +++ b/scene/3d/navigation_obstacle_3d.h @@ -57,6 +57,9 @@ class NavigationObstacle3D : public Node3D { Vector3 previous_velocity; bool velocity_submitted = false; + bool affect_navigation_mesh = false; + bool carve_navigation_mesh = false; + #ifdef DEBUG_ENABLED RID fake_agent_radius_debug_instance; Ref<ArrayMesh> fake_agent_radius_debug_mesh; @@ -108,6 +111,12 @@ public: void _avoidance_done(Vector3 p_new_velocity); // Dummy + void set_affect_navigation_mesh(bool p_enabled); + bool get_affect_navigation_mesh() const; + + void set_carve_navigation_mesh(bool p_enabled); + bool get_carve_navigation_mesh() const; + private: void _update_map(RID p_map); void _update_position(const Vector3 p_position); diff --git a/scene/gui/control.cpp b/scene/gui/control.cpp index 974ecd1edc..d65399446c 100644 --- a/scene/gui/control.cpp +++ b/scene/gui/control.cpp @@ -1406,15 +1406,11 @@ void Control::_set_global_position(const Point2 &p_point) { void Control::set_global_position(const Point2 &p_point, bool p_keep_offsets) { ERR_MAIN_THREAD_GUARD; - - Transform2D global_transform_cache = get_global_transform(); - if (p_point == global_transform_cache.get_origin()) { - return; // Edge case, but avoids calculation. - } - - Point2 internal_position = global_transform_cache.affine_inverse().xform(p_point); - - set_position(internal_position + data.pos_cache, p_keep_offsets); + // (parent_global_transform * T(new_position) * internal_transform).origin == new_global_position + // (T(new_position) * internal_transform).origin == new_position_in_parent_space + // new_position == new_position_in_parent_space - internal_transform.origin + Point2 position_in_parent_space = data.parent_canvas_item ? data.parent_canvas_item->get_global_transform().affine_inverse().xform(p_point) : p_point; + set_position(position_in_parent_space - _get_internal_transform().get_origin(), p_keep_offsets); } Point2 Control::get_global_position() const { diff --git a/scene/gui/label.cpp b/scene/gui/label.cpp index 56da1332e7..42b4e56b48 100644 --- a/scene/gui/label.cpp +++ b/scene/gui/label.cpp @@ -316,7 +316,7 @@ inline void draw_glyph(const Glyph &p_gl, const RID &p_canvas, const Color &p_fo } } -inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_color, const Color &p_font_shadow_color, const Color &p_font_outline_color, const int &p_shadow_outline_size, const int &p_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { +inline void draw_glyph_shadow(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_shadow_color, int p_shadow_outline_size, const Vector2 &p_ofs, const Vector2 &shadow_ofs) { if (p_gl.font_rid != RID()) { if (p_font_shadow_color.a > 0) { TS->font_draw_glyph(p_gl.font_rid, p_canvas, p_gl.font_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); @@ -324,6 +324,11 @@ inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Col if (p_font_shadow_color.a > 0 && p_shadow_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_shadow_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off) + shadow_ofs, p_gl.index, p_font_shadow_color); } + } +} + +inline void draw_glyph_outline(const Glyph &p_gl, const RID &p_canvas, const Color &p_font_outline_color, int p_outline_size, const Vector2 &p_ofs) { + if (p_gl.font_rid != RID()) { if (p_font_outline_color.a != 0.0 && p_outline_size > 0) { TS->font_draw_glyph_outline(p_gl.font_rid, p_canvas, p_gl.font_size, p_outline_size, p_ofs + Vector2(p_gl.x_off, p_gl.y_off), p_gl.index, p_font_outline_color); } @@ -537,25 +542,36 @@ void Label::_notification(int p_what) { const Glyph *ellipsis_glyphs = TS->shaped_text_get_ellipsis_glyphs(lines_rid[i]); int ellipsis_gl_size = TS->shaped_text_get_ellipsis_glyph_count(lines_rid[i]); - // Draw outline. Note: Do not merge this into the single loop with the main text, to prevent overlaps. - int processed_glyphs_ol = processed_glyphs; - if ((outline_size > 0 && font_outline_color.a != 0) || (font_shadow_color.a != 0)) { - Vector2 offset = ofs; + // Draw shadow, outline and text. Note: Do not merge this into the single loop iteration, to prevent overlaps. + for (int step = DRAW_STEP_SHADOW; step < DRAW_STEP_MAX; step++) { + if (step == DRAW_STEP_SHADOW && (font_shadow_color.a == 0)) { + continue; + } + if (step == DRAW_STEP_OUTLINE && (outline_size <= 0 || font_outline_color.a == 0)) { + continue; + } + + int processed_glyphs_step = processed_glyphs; + Vector2 offset_step = ofs; // Draw RTL ellipsis string when necessary. if (rtl && ellipsis_pos >= 0) { for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) { for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); - //Draw glyph outlines and shadow. + bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs)); if (!skip) { - draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + if (step == DRAW_STEP_SHADOW) { + draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs); + } else if (step == DRAW_STEP_OUTLINE) { + draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_outline_color, outline_size, offset_step); + } else if (step == DRAW_STEP_TEXT) { + draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, offset_step); + } } - processed_glyphs_ol++; - offset.x += ellipsis_glyphs[gl_idx].advance; + processed_glyphs_step++; + offset_step.x += ellipsis_glyphs[gl_idx].advance; } } } - // Draw main text. for (int j = 0; j < gl_size; j++) { // Trim when necessary. @@ -571,85 +587,37 @@ void Label::_notification(int p_what) { } } for (int k = 0; k < glyphs[j].repeat; k++) { - bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); - - // Draw glyph outlines and shadow. + bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs)); if (!skip) { - draw_glyph_outline(glyphs[j], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + if (step == DRAW_STEP_SHADOW) { + draw_glyph_shadow(glyphs[j], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs); + } else if (step == DRAW_STEP_OUTLINE) { + draw_glyph_outline(glyphs[j], ci, font_outline_color, outline_size, offset_step); + } else if (step == DRAW_STEP_TEXT) { + draw_glyph(glyphs[j], ci, font_color, offset_step); + } } - processed_glyphs_ol++; - offset.x += glyphs[j].advance; + processed_glyphs_step++; + offset_step.x += glyphs[j].advance; } } // Draw LTR ellipsis string when necessary. if (!rtl && ellipsis_pos >= 0) { for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) { for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_ol >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_ol < total_glyphs - visible_glyphs)); - //Draw glyph outlines and shadow. + bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs_step >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs_step < total_glyphs - visible_glyphs)); if (!skip) { - draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_color, font_shadow_color, font_outline_color, shadow_outline_size, outline_size, offset, shadow_ofs); + if (step == DRAW_STEP_SHADOW) { + draw_glyph_shadow(ellipsis_glyphs[gl_idx], ci, font_shadow_color, shadow_outline_size, offset_step, shadow_ofs); + } else if (step == DRAW_STEP_OUTLINE) { + draw_glyph_outline(ellipsis_glyphs[gl_idx], ci, font_outline_color, outline_size, offset_step); + } else if (step == DRAW_STEP_TEXT) { + draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, offset_step); + } } - processed_glyphs_ol++; - offset.x += ellipsis_glyphs[gl_idx].advance; - } - } - } - } - - // Draw main text. Note: Do not merge this into the single loop with the outline, to prevent overlaps. - - // Draw RTL ellipsis string when necessary. - if (rtl && ellipsis_pos >= 0) { - for (int gl_idx = ellipsis_gl_size - 1; gl_idx >= 0; gl_idx--) { - for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); - //Draw glyph outlines and shadow. - if (!skip) { - draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs); - } - processed_glyphs++; - ofs.x += ellipsis_glyphs[gl_idx].advance; - } - } - } - - // Draw main text. - for (int j = 0; j < gl_size; j++) { - // Trim when necessary. - if (trim_pos >= 0) { - if (rtl) { - if (j < trim_pos) { - continue; - } - } else { - if (j >= trim_pos) { - break; - } - } - } - for (int k = 0; k < glyphs[j].repeat; k++) { - bool skip = (trim_chars && glyphs[j].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); - - // Draw glyph outlines and shadow. - if (!skip) { - draw_glyph(glyphs[j], ci, font_color, ofs); - } - processed_glyphs++; - ofs.x += glyphs[j].advance; - } - } - // Draw LTR ellipsis string when necessary. - if (!rtl && ellipsis_pos >= 0) { - for (int gl_idx = 0; gl_idx < ellipsis_gl_size; gl_idx++) { - for (int j = 0; j < ellipsis_glyphs[gl_idx].repeat; j++) { - bool skip = (trim_chars && ellipsis_glyphs[gl_idx].end > visible_chars) || (trim_glyphs_ltr && (processed_glyphs >= visible_glyphs)) || (trim_glyphs_rtl && (processed_glyphs < total_glyphs - visible_glyphs)); - //Draw glyph outlines and shadow. - if (!skip) { - draw_glyph(ellipsis_glyphs[gl_idx], ci, font_color, ofs); + processed_glyphs_step++; + offset_step.x += ellipsis_glyphs[gl_idx].advance; } - processed_glyphs++; - ofs.x += ellipsis_glyphs[gl_idx].advance; } } } diff --git a/scene/gui/label.h b/scene/gui/label.h index 4bd0e53605..e0ebca944a 100644 --- a/scene/gui/label.h +++ b/scene/gui/label.h @@ -38,6 +38,13 @@ class Label : public Control { GDCLASS(Label, Control); private: + enum LabelDrawStep { + DRAW_STEP_SHADOW, + DRAW_STEP_OUTLINE, + DRAW_STEP_TEXT, + DRAW_STEP_MAX, + }; + HorizontalAlignment horizontal_alignment = HORIZONTAL_ALIGNMENT_LEFT; VerticalAlignment vertical_alignment = VERTICAL_ALIGNMENT_TOP; String text; diff --git a/scene/gui/popup_menu.cpp b/scene/gui/popup_menu.cpp index 25d999851b..56346f5edc 100644 --- a/scene/gui/popup_menu.cpp +++ b/scene/gui/popup_menu.cpp @@ -92,7 +92,7 @@ RID PopupMenu::bind_global_menu() { NativeMenu *nmenu = NativeMenu::get_singleton(); - if (system_menu_id != NativeMenu::INVALID_MENU_ID) { + if (system_menu_id != NativeMenu::INVALID_MENU_ID && nmenu->has_system_menu(system_menu_id)) { if (system_menus.has(system_menu_id)) { WARN_PRINT(vformat("Attempting to bind PopupMenu to the system menu %s, but another menu is already bound to it. This menu: %s, current menu: %s", nmenu->get_system_menu_name(system_menu_id), get_description(), system_menus[system_menu_id]->get_description())); global_menu = nmenu->create_menu(); diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp index 7c33aa9e38..dfa3b598c8 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_2d.cpp +++ b/scene/resources/navigation_mesh_source_geometry_data_2d.cpp @@ -35,6 +35,12 @@ void NavigationMeshSourceGeometryData2D::clear() { traversable_outlines.clear(); obstruction_outlines.clear(); + clear_projected_obstructions(); +} + +void NavigationMeshSourceGeometryData2D::clear_projected_obstructions() { + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.clear(); } void NavigationMeshSourceGeometryData2D::_set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines) { @@ -117,6 +123,109 @@ void NavigationMeshSourceGeometryData2D::merge(const Ref<NavigationMeshSourceGeo // No need to worry about `root_node_transform` here as the data is already xformed. traversable_outlines.append_array(p_other_geometry->traversable_outlines); obstruction_outlines.append_array(p_other_geometry->obstruction_outlines); + + if (p_other_geometry->_projected_obstructions.size() > 0) { + RWLockWrite write_lock(geometry_rwlock); + + for (const ProjectedObstruction &other_projected_obstruction : p_other_geometry->_projected_obstructions) { + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(other_projected_obstruction.vertices.size()); + + const float *other_obstruction_vertices_ptr = other_projected_obstruction.vertices.ptr(); + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + for (int j = 0; j < other_projected_obstruction.vertices.size(); j++) { + obstruction_vertices_ptrw[j] = other_obstruction_vertices_ptr[j]; + } + + projected_obstruction.carve = other_projected_obstruction.carve; + + _projected_obstructions.push_back(projected_obstruction); + } + } +} + +void NavigationMeshSourceGeometryData2D::add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve) { + ERR_FAIL_COND(p_vertices.size() < 2); + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(p_vertices.size() * 2); + projected_obstruction.carve = p_carve; + + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + int vertex_index = 0; + for (const Vector2 &vertex : p_vertices) { + obstruction_vertices_ptrw[vertex_index++] = vertex.x; + obstruction_vertices_ptrw[vertex_index++] = vertex.y; + } + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); +} + +void NavigationMeshSourceGeometryData2D::set_projected_obstructions(const Array &p_array) { + clear_projected_obstructions(); + + for (int i = 0; i < p_array.size(); i++) { + Dictionary data = p_array[i]; + ERR_FAIL_COND(!data.has("version")); + + uint32_t po_version = data["version"]; + + if (po_version == 1) { + ERR_FAIL_COND(!data.has("vertices")); + ERR_FAIL_COND(!data.has("carve")); + } + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices = Vector<float>(data["vertices"]); + projected_obstruction.carve = data["carve"]; + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); + } +} + +Vector<NavigationMeshSourceGeometryData2D::ProjectedObstruction> NavigationMeshSourceGeometryData2D::_get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + return _projected_obstructions; +} + +Array NavigationMeshSourceGeometryData2D::get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + + Array ret; + ret.resize(_projected_obstructions.size()); + + for (int i = 0; i < _projected_obstructions.size(); i++) { + const ProjectedObstruction &projected_obstruction = _projected_obstructions[i]; + + Dictionary data; + data["version"] = (int)ProjectedObstruction::VERSION; + data["vertices"] = projected_obstruction.vertices; + data["carve"] = projected_obstruction.carve; + + ret[i] = data; + } + + return ret; +} + +bool NavigationMeshSourceGeometryData2D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "projected_obstructions") { + set_projected_obstructions(p_value); + return true; + } + return false; +} + +bool NavigationMeshSourceGeometryData2D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "projected_obstructions") { + r_ret = get_projected_obstructions(); + return true; + } + return false; } void NavigationMeshSourceGeometryData2D::_bind_methods() { @@ -134,6 +243,12 @@ void NavigationMeshSourceGeometryData2D::_bind_methods() { ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData2D::merge); + ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "carve"), &NavigationMeshSourceGeometryData2D::add_projected_obstruction); + ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData2D::clear_projected_obstructions); + ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData2D::set_projected_obstructions); + ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData2D::get_projected_obstructions); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "traversable_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_traversable_outlines", "get_traversable_outlines"); ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "obstruction_outlines", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_obstruction_outlines", "get_obstruction_outlines"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions"); } diff --git a/scene/resources/navigation_mesh_source_geometry_data_2d.h b/scene/resources/navigation_mesh_source_geometry_data_2d.h index 4accdbc1f4..0e321fbeb9 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_2d.h +++ b/scene/resources/navigation_mesh_source_geometry_data_2d.h @@ -31,19 +31,36 @@ #ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H #define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_2D_H +#include "core/os/rw_lock.h" #include "scene/2d/node_2d.h" #include "scene/resources/navigation_polygon.h" class NavigationMeshSourceGeometryData2D : public Resource { GDCLASS(NavigationMeshSourceGeometryData2D, Resource); + RWLock geometry_rwlock; Vector<Vector<Vector2>> traversable_outlines; Vector<Vector<Vector2>> obstruction_outlines; +public: + struct ProjectedObstruction; + +private: + Vector<ProjectedObstruction> _projected_obstructions; + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; static void _bind_methods(); public: + struct ProjectedObstruction { + static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility. + + Vector<float> vertices; + bool carve = false; + }; + void _set_traversable_outlines(const Vector<Vector<Vector2>> &p_traversable_outlines); const Vector<Vector<Vector2>> &_get_traversable_outlines() const { return traversable_outlines; } @@ -70,6 +87,13 @@ public: bool has_data() { return traversable_outlines.size(); }; void clear(); + void clear_projected_obstructions(); + + void add_projected_obstruction(const Vector<Vector2> &p_vertices, bool p_carve); + Vector<ProjectedObstruction> _get_projected_obstructions() const; + + void set_projected_obstructions(const Array &p_array); + Array get_projected_obstructions() const; void merge(const Ref<NavigationMeshSourceGeometryData2D> &p_other_geometry); diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp index 43fb592bba..1bd98fe1ac 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_3d.cpp +++ b/scene/resources/navigation_mesh_source_geometry_data_3d.cpp @@ -41,6 +41,12 @@ void NavigationMeshSourceGeometryData3D::set_indices(const Vector<int> &p_indice void NavigationMeshSourceGeometryData3D::clear() { vertices.clear(); indices.clear(); + clear_projected_obstructions(); +} + +void NavigationMeshSourceGeometryData3D::clear_projected_obstructions() { + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.clear(); } void NavigationMeshSourceGeometryData3D::_add_vertex(const Vector3 &p_vec3) { @@ -174,6 +180,121 @@ void NavigationMeshSourceGeometryData3D::merge(const Ref<NavigationMeshSourceGeo for (int64_t i = number_of_indices_before_merge; i < indices.size(); i++) { indices.set(i, indices[i] + number_of_vertices_before_merge / 3); } + + if (p_other_geometry->_projected_obstructions.size() > 0) { + RWLockWrite write_lock(geometry_rwlock); + + for (const ProjectedObstruction &other_projected_obstruction : p_other_geometry->_projected_obstructions) { + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(other_projected_obstruction.vertices.size()); + + const float *other_obstruction_vertices_ptr = other_projected_obstruction.vertices.ptr(); + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + for (int j = 0; j < other_projected_obstruction.vertices.size(); j++) { + obstruction_vertices_ptrw[j] = other_obstruction_vertices_ptr[j]; + } + + projected_obstruction.elevation = other_projected_obstruction.elevation; + projected_obstruction.height = other_projected_obstruction.height; + projected_obstruction.carve = other_projected_obstruction.carve; + + _projected_obstructions.push_back(projected_obstruction); + } + } +} + +void NavigationMeshSourceGeometryData3D::add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve) { + ERR_FAIL_COND(p_vertices.size() < 3); + ERR_FAIL_COND(p_height < 0.0); + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices.resize(p_vertices.size() * 3); + projected_obstruction.elevation = p_elevation; + projected_obstruction.height = p_height; + projected_obstruction.carve = p_carve; + + float *obstruction_vertices_ptrw = projected_obstruction.vertices.ptrw(); + + int vertex_index = 0; + for (const Vector3 &vertex : p_vertices) { + obstruction_vertices_ptrw[vertex_index++] = vertex.x; + obstruction_vertices_ptrw[vertex_index++] = vertex.y; + obstruction_vertices_ptrw[vertex_index++] = vertex.z; + } + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); +} + +void NavigationMeshSourceGeometryData3D::set_projected_obstructions(const Array &p_array) { + clear_projected_obstructions(); + + for (int i = 0; i < p_array.size(); i++) { + Dictionary data = p_array[i]; + ERR_FAIL_COND(!data.has("version")); + + uint32_t po_version = data["version"]; + + if (po_version == 1) { + ERR_FAIL_COND(!data.has("vertices")); + ERR_FAIL_COND(!data.has("elevation")); + ERR_FAIL_COND(!data.has("height")); + ERR_FAIL_COND(!data.has("carve")); + } + + ProjectedObstruction projected_obstruction; + projected_obstruction.vertices = Vector<float>(data["vertices"]); + projected_obstruction.elevation = data["elevation"]; + projected_obstruction.height = data["height"]; + projected_obstruction.carve = data["carve"]; + + RWLockWrite write_lock(geometry_rwlock); + _projected_obstructions.push_back(projected_obstruction); + } +} + +Vector<NavigationMeshSourceGeometryData3D::ProjectedObstruction> NavigationMeshSourceGeometryData3D::_get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + return _projected_obstructions; +} + +Array NavigationMeshSourceGeometryData3D::get_projected_obstructions() const { + RWLockRead read_lock(geometry_rwlock); + + Array ret; + ret.resize(_projected_obstructions.size()); + + for (int i = 0; i < _projected_obstructions.size(); i++) { + const ProjectedObstruction &projected_obstruction = _projected_obstructions[i]; + + Dictionary data; + data["version"] = (int)ProjectedObstruction::VERSION; + data["vertices"] = projected_obstruction.vertices; + data["elevation"] = projected_obstruction.elevation; + data["height"] = projected_obstruction.height; + data["carve"] = projected_obstruction.carve; + + ret[i] = data; + } + + return ret; +} + +bool NavigationMeshSourceGeometryData3D::_set(const StringName &p_name, const Variant &p_value) { + if (p_name == "projected_obstructions") { + set_projected_obstructions(p_value); + return true; + } + return false; +} + +bool NavigationMeshSourceGeometryData3D::_get(const StringName &p_name, Variant &r_ret) const { + if (p_name == "projected_obstructions") { + r_ret = get_projected_obstructions(); + return true; + } + return false; } void NavigationMeshSourceGeometryData3D::_bind_methods() { @@ -191,6 +312,12 @@ void NavigationMeshSourceGeometryData3D::_bind_methods() { ClassDB::bind_method(D_METHOD("add_faces", "faces", "xform"), &NavigationMeshSourceGeometryData3D::add_faces); ClassDB::bind_method(D_METHOD("merge", "other_geometry"), &NavigationMeshSourceGeometryData3D::merge); + ClassDB::bind_method(D_METHOD("add_projected_obstruction", "vertices", "elevation", "height", "carve"), &NavigationMeshSourceGeometryData3D::add_projected_obstruction); + ClassDB::bind_method(D_METHOD("clear_projected_obstructions"), &NavigationMeshSourceGeometryData3D::clear_projected_obstructions); + ClassDB::bind_method(D_METHOD("set_projected_obstructions", "projected_obstructions"), &NavigationMeshSourceGeometryData3D::set_projected_obstructions); + ClassDB::bind_method(D_METHOD("get_projected_obstructions"), &NavigationMeshSourceGeometryData3D::get_projected_obstructions); + ADD_PROPERTY(PropertyInfo(Variant::PACKED_VECTOR3_ARRAY, "vertices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_vertices", "get_vertices"); ADD_PROPERTY(PropertyInfo(Variant::PACKED_INT32_ARRAY, "indices", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_indices", "get_indices"); + ADD_PROPERTY(PropertyInfo(Variant::ARRAY, "projected_obstructions", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_NO_EDITOR | PROPERTY_USAGE_INTERNAL), "set_projected_obstructions", "get_projected_obstructions"); } diff --git a/scene/resources/navigation_mesh_source_geometry_data_3d.h b/scene/resources/navigation_mesh_source_geometry_data_3d.h index 981f20a74b..79e2f3740d 100644 --- a/scene/resources/navigation_mesh_source_geometry_data_3d.h +++ b/scene/resources/navigation_mesh_source_geometry_data_3d.h @@ -31,15 +31,25 @@ #ifndef NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H #define NAVIGATION_MESH_SOURCE_GEOMETRY_DATA_3D_H +#include "core/os/rw_lock.h" #include "scene/resources/mesh.h" class NavigationMeshSourceGeometryData3D : public Resource { GDCLASS(NavigationMeshSourceGeometryData3D, Resource); + RWLock geometry_rwlock; Vector<float> vertices; Vector<int> indices; +public: + struct ProjectedObstruction; + +private: + Vector<ProjectedObstruction> _projected_obstructions; + protected: + bool _set(const StringName &p_name, const Variant &p_value); + bool _get(const StringName &p_name, Variant &r_ret) const; static void _bind_methods(); private: @@ -49,6 +59,15 @@ private: void _add_faces(const PackedVector3Array &p_faces, const Transform3D &p_xform); public: + struct ProjectedObstruction { + static inline uint32_t VERSION = 1; // Increase when format changes so we can detect outdated formats and provide compatibility. + + Vector<float> vertices; + float elevation = 0.0; + float height = 0.0; + bool carve = false; + }; + // kept root node transform here on the geometry data // if we add this transform to all exposed functions we need to break comp on all functions later // when navmesh changes from global transform to relative to navregion @@ -63,6 +82,7 @@ public: bool has_data() { return vertices.size() && indices.size(); }; void clear(); + void clear_projected_obstructions(); void add_mesh(const Ref<Mesh> &p_mesh, const Transform3D &p_xform); void add_mesh_array(const Array &p_mesh_array, const Transform3D &p_xform); @@ -70,6 +90,12 @@ public: void merge(const Ref<NavigationMeshSourceGeometryData3D> &p_other_geometry); + void add_projected_obstruction(const Vector<Vector3> &p_vertices, float p_elevation, float p_height, bool p_carve); + Vector<ProjectedObstruction> _get_projected_obstructions() const; + + void set_projected_obstructions(const Array &p_array); + Array get_projected_obstructions() const; + NavigationMeshSourceGeometryData3D() {} ~NavigationMeshSourceGeometryData3D() { clear(); } }; diff --git a/thirdparty/README.md b/thirdparty/README.md index 2c9bc36545..d86c14cd14 100644 --- a/thirdparty/README.md +++ b/thirdparty/README.md @@ -769,9 +769,6 @@ Files extracted from upstream source: - `src/sljit/` - `AUTHORS` and `LICENCE` -A sljit patch from upstream was backported to fix macOS < 11.0 compilation -in 10.40, it can be found in the `patches` folder. - ## recastnavigation diff --git a/thirdparty/pcre2/patches/sljit-macos11-conditional.patch b/thirdparty/pcre2/patches/sljit-macos11-conditional.patch deleted file mode 100644 index b92c8f9e09..0000000000 --- a/thirdparty/pcre2/patches/sljit-macos11-conditional.patch +++ /dev/null @@ -1,31 +0,0 @@ -From de8fc816bc6698ab97316ed954e133e7e5098262 Mon Sep 17 00:00:00 2001 -From: =?UTF-8?q?Carlo=20Marcelo=20Arenas=20Bel=C3=B3n?= <carenas@gmail.com> -Date: Thu, 21 Apr 2022 21:01:12 -0700 -Subject: [PATCH] macos: somehow allow building with a target below 11.0 - -While building for macOS older than 11 in Apple Silicon makes no -sense, some build systems lack the flexibility to set a target per -architecture while aiming to support multi architecture binaries. - -Allow an option in those cases by using the slower runtime checks -if the toolchain allows it. - -Fixes: PCRE2Project/pcre2#109 ---- - thirdparty/pcre2/src/sljit/sljitExecAllocator.c | 3 +++ - 1 file changed, 3 insertions(+) - -diff --git a/thirdparty/pcre2/src/sljit/sljitExecAllocator.c b/thirdparty/pcre2/src/sljit/sljitExecAllocator.c -index 92d940ddc2..6359848cd5 100644 ---- a/thirdparty/pcre2/src/sljit/sljitExecAllocator.c -+++ b/thirdparty/pcre2/src/sljit/sljitExecAllocator.c -@@ -152,6 +152,9 @@ static SLJIT_INLINE void apple_update_wx_flags(sljit_s32 enable_exec) - { - #if MAC_OS_X_VERSION_MIN_REQUIRED >= 110000 - pthread_jit_write_protect_np(enable_exec); -+#elif defined(__clang__) -+ if (__builtin_available(macOS 11.0, *)) -+ pthread_jit_write_protect_np(enable_exec); - #else - #error "Must target Big Sur or newer" - #endif /* BigSur */ diff --git a/thirdparty/pcre2/src/sljit/sljitExecAllocator.c b/thirdparty/pcre2/src/sljit/sljitExecAllocator.c deleted file mode 100644 index 6359848cd5..0000000000 --- a/thirdparty/pcre2/src/sljit/sljitExecAllocator.c +++ /dev/null @@ -1,414 +0,0 @@ -/* - * Stack-less Just-In-Time compiler - * - * Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - This file contains a simple executable memory allocator - - It is assumed, that executable code blocks are usually medium (or sometimes - large) memory blocks, and the allocator is not too frequently called (less - optimized than other allocators). Thus, using it as a generic allocator is - not suggested. - - How does it work: - Memory is allocated in continuous memory areas called chunks by alloc_chunk() - Chunk format: - [ block ][ block ] ... [ block ][ block terminator ] - - All blocks and the block terminator is started with block_header. The block - header contains the size of the previous and the next block. These sizes - can also contain special values. - Block size: - 0 - The block is a free_block, with a different size member. - 1 - The block is a block terminator. - n - The block is used at the moment, and the value contains its size. - Previous block size: - 0 - This is the first block of the memory chunk. - n - The size of the previous block. - - Using these size values we can go forward or backward on the block chain. - The unused blocks are stored in a chain list pointed by free_blocks. This - list is useful if we need to find a suitable memory area when the allocator - is called. - - When a block is freed, the new free block is connected to its adjacent free - blocks if possible. - - [ free block ][ used block ][ free block ] - and "used block" is freed, the three blocks are connected together: - [ one big free block ] -*/ - -/* --------------------------------------------------------------------- */ -/* System (OS) functions */ -/* --------------------------------------------------------------------- */ - -/* 64 KByte. */ -#define CHUNK_SIZE (sljit_uw)0x10000u - -/* - alloc_chunk / free_chunk : - * allocate executable system memory chunks - * the size is always divisible by CHUNK_SIZE - SLJIT_ALLOCATOR_LOCK / SLJIT_ALLOCATOR_UNLOCK : - * provided as part of sljitUtils - * only the allocator requires this lock, sljit is fully thread safe - as it only uses local variables -*/ - -#ifdef _WIN32 -#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) - -static SLJIT_INLINE void* alloc_chunk(sljit_uw size) -{ - return VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE); -} - -static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size) -{ - SLJIT_UNUSED_ARG(size); - VirtualFree(chunk, 0, MEM_RELEASE); -} - -#else /* POSIX */ - -#if defined(__APPLE__) && defined(MAP_JIT) -/* - On macOS systems, returns MAP_JIT if it is defined _and_ we're running on a - version where it's OK to have more than one JIT block or where MAP_JIT is - required. - On non-macOS systems, returns MAP_JIT if it is defined. -*/ -#include <TargetConditionals.h> -#if TARGET_OS_OSX -#if defined SLJIT_CONFIG_X86 && SLJIT_CONFIG_X86 -#ifdef MAP_ANON -#include <sys/utsname.h> -#include <stdlib.h> - -#define SLJIT_MAP_JIT (get_map_jit_flag()) - -static SLJIT_INLINE int get_map_jit_flag() -{ - size_t page_size; - void *ptr; - struct utsname name; - static int map_jit_flag = -1; - - if (map_jit_flag < 0) { - map_jit_flag = 0; - uname(&name); - - /* Kernel version for 10.14.0 (Mojave) or later */ - if (atoi(name.release) >= 18) { - page_size = get_page_alignment() + 1; - /* Only use MAP_JIT if a hardened runtime is used */ - ptr = mmap(NULL, page_size, PROT_WRITE | PROT_EXEC, - MAP_PRIVATE | MAP_ANON, -1, 0); - - if (ptr != MAP_FAILED) - munmap(ptr, page_size); - else - map_jit_flag = MAP_JIT; - } - } - return map_jit_flag; -} -#endif /* MAP_ANON */ -#else /* !SLJIT_CONFIG_X86 */ -#if !(defined SLJIT_CONFIG_ARM && SLJIT_CONFIG_ARM) -#error "Unsupported architecture" -#endif /* SLJIT_CONFIG_ARM */ -#include <AvailabilityMacros.h> -#include <pthread.h> - -#define SLJIT_MAP_JIT (MAP_JIT) -#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) \ - apple_update_wx_flags(enable_exec) - -static SLJIT_INLINE void apple_update_wx_flags(sljit_s32 enable_exec) -{ -#if MAC_OS_X_VERSION_MIN_REQUIRED >= 110000 - pthread_jit_write_protect_np(enable_exec); -#elif defined(__clang__) - if (__builtin_available(macOS 11.0, *)) - pthread_jit_write_protect_np(enable_exec); -#else -#error "Must target Big Sur or newer" -#endif /* BigSur */ -} -#endif /* SLJIT_CONFIG_X86 */ -#else /* !TARGET_OS_OSX */ -#define SLJIT_MAP_JIT (MAP_JIT) -#endif /* TARGET_OS_OSX */ -#endif /* __APPLE__ && MAP_JIT */ -#ifndef SLJIT_UPDATE_WX_FLAGS -#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) -#endif /* !SLJIT_UPDATE_WX_FLAGS */ -#ifndef SLJIT_MAP_JIT -#define SLJIT_MAP_JIT (0) -#endif /* !SLJIT_MAP_JIT */ - -static SLJIT_INLINE void* alloc_chunk(sljit_uw size) -{ - void *retval; - int prot = PROT_READ | PROT_WRITE | PROT_EXEC; - int flags = MAP_PRIVATE; - int fd = -1; - -#ifdef PROT_MAX - prot |= PROT_MAX(prot); -#endif - -#ifdef MAP_ANON - flags |= MAP_ANON | SLJIT_MAP_JIT; -#else /* !MAP_ANON */ - if (SLJIT_UNLIKELY((dev_zero < 0) && open_dev_zero())) - return NULL; - - fd = dev_zero; -#endif /* MAP_ANON */ - - retval = mmap(NULL, size, prot, flags, fd, 0); - if (retval == MAP_FAILED) - return NULL; - -#ifdef __FreeBSD__ - /* HardenedBSD's mmap lies, so check permissions again */ - if (mprotect(retval, size, PROT_READ | PROT_WRITE | PROT_EXEC) < 0) { - munmap(retval, size); - return NULL; - } -#endif /* FreeBSD */ - - SLJIT_UPDATE_WX_FLAGS(retval, (uint8_t *)retval + size, 0); - - return retval; -} - -static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size) -{ - munmap(chunk, size); -} - -#endif /* windows */ - -/* --------------------------------------------------------------------- */ -/* Common functions */ -/* --------------------------------------------------------------------- */ - -#define CHUNK_MASK (~(CHUNK_SIZE - 1)) - -struct block_header { - sljit_uw size; - sljit_uw prev_size; -}; - -struct free_block { - struct block_header header; - struct free_block *next; - struct free_block *prev; - sljit_uw size; -}; - -#define AS_BLOCK_HEADER(base, offset) \ - ((struct block_header*)(((sljit_u8*)base) + offset)) -#define AS_FREE_BLOCK(base, offset) \ - ((struct free_block*)(((sljit_u8*)base) + offset)) -#define MEM_START(base) ((void*)(((sljit_u8*)base) + sizeof(struct block_header))) -#define ALIGN_SIZE(size) (((size) + sizeof(struct block_header) + 7u) & ~(sljit_uw)7) - -static struct free_block* free_blocks; -static sljit_uw allocated_size; -static sljit_uw total_size; - -static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size) -{ - free_block->header.size = 0; - free_block->size = size; - - free_block->next = free_blocks; - free_block->prev = NULL; - if (free_blocks) - free_blocks->prev = free_block; - free_blocks = free_block; -} - -static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block) -{ - if (free_block->next) - free_block->next->prev = free_block->prev; - - if (free_block->prev) - free_block->prev->next = free_block->next; - else { - SLJIT_ASSERT(free_blocks == free_block); - free_blocks = free_block->next; - } -} - -SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size) -{ - struct block_header *header; - struct block_header *next_header; - struct free_block *free_block; - sljit_uw chunk_size; - - SLJIT_ALLOCATOR_LOCK(); - if (size < (64 - sizeof(struct block_header))) - size = (64 - sizeof(struct block_header)); - size = ALIGN_SIZE(size); - - free_block = free_blocks; - while (free_block) { - if (free_block->size >= size) { - chunk_size = free_block->size; - SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0); - if (chunk_size > size + 64) { - /* We just cut a block from the end of the free block. */ - chunk_size -= size; - free_block->size = chunk_size; - header = AS_BLOCK_HEADER(free_block, chunk_size); - header->prev_size = chunk_size; - AS_BLOCK_HEADER(header, size)->prev_size = size; - } - else { - sljit_remove_free_block(free_block); - header = (struct block_header*)free_block; - size = chunk_size; - } - allocated_size += size; - header->size = size; - SLJIT_ALLOCATOR_UNLOCK(); - return MEM_START(header); - } - free_block = free_block->next; - } - - chunk_size = (size + sizeof(struct block_header) + CHUNK_SIZE - 1) & CHUNK_MASK; - header = (struct block_header*)alloc_chunk(chunk_size); - if (!header) { - SLJIT_ALLOCATOR_UNLOCK(); - return NULL; - } - - chunk_size -= sizeof(struct block_header); - total_size += chunk_size; - - header->prev_size = 0; - if (chunk_size > size + 64) { - /* Cut the allocated space into a free and a used block. */ - allocated_size += size; - header->size = size; - chunk_size -= size; - - free_block = AS_FREE_BLOCK(header, size); - free_block->header.prev_size = size; - sljit_insert_free_block(free_block, chunk_size); - next_header = AS_BLOCK_HEADER(free_block, chunk_size); - } - else { - /* All space belongs to this allocation. */ - allocated_size += chunk_size; - header->size = chunk_size; - next_header = AS_BLOCK_HEADER(header, chunk_size); - } - next_header->size = 1; - next_header->prev_size = chunk_size; - SLJIT_ALLOCATOR_UNLOCK(); - return MEM_START(header); -} - -SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr) -{ - struct block_header *header; - struct free_block* free_block; - - SLJIT_ALLOCATOR_LOCK(); - header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header)); - allocated_size -= header->size; - - /* Connecting free blocks together if possible. */ - SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0); - - /* If header->prev_size == 0, free_block will equal to header. - In this case, free_block->header.size will be > 0. */ - free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size); - if (SLJIT_UNLIKELY(!free_block->header.size)) { - free_block->size += header->size; - header = AS_BLOCK_HEADER(free_block, free_block->size); - header->prev_size = free_block->size; - } - else { - free_block = (struct free_block*)header; - sljit_insert_free_block(free_block, header->size); - } - - header = AS_BLOCK_HEADER(free_block, free_block->size); - if (SLJIT_UNLIKELY(!header->size)) { - free_block->size += ((struct free_block*)header)->size; - sljit_remove_free_block((struct free_block*)header); - header = AS_BLOCK_HEADER(free_block, free_block->size); - header->prev_size = free_block->size; - } - - /* The whole chunk is free. */ - if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) { - /* If this block is freed, we still have (allocated_size / 2) free space. */ - if (total_size - free_block->size > (allocated_size * 3 / 2)) { - total_size -= free_block->size; - sljit_remove_free_block(free_block); - free_chunk(free_block, free_block->size + sizeof(struct block_header)); - } - } - - SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 1); - SLJIT_ALLOCATOR_UNLOCK(); -} - -SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void) -{ - struct free_block* free_block; - struct free_block* next_free_block; - - SLJIT_ALLOCATOR_LOCK(); - SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 0); - - free_block = free_blocks; - while (free_block) { - next_free_block = free_block->next; - if (!free_block->header.prev_size && - AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) { - total_size -= free_block->size; - sljit_remove_free_block(free_block); - free_chunk(free_block, free_block->size + sizeof(struct block_header)); - } - free_block = next_free_block; - } - - SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks)); - SLJIT_UPDATE_WX_FLAGS(NULL, NULL, 1); - SLJIT_ALLOCATOR_UNLOCK(); -} diff --git a/thirdparty/pcre2/src/sljit/sljitProtExecAllocator.c b/thirdparty/pcre2/src/sljit/sljitProtExecAllocator.c deleted file mode 100644 index 915411fbed..0000000000 --- a/thirdparty/pcre2/src/sljit/sljitProtExecAllocator.c +++ /dev/null @@ -1,474 +0,0 @@ -/* - * Stack-less Just-In-Time compiler - * - * Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - This file contains a simple executable memory allocator - - It is assumed, that executable code blocks are usually medium (or sometimes - large) memory blocks, and the allocator is not too frequently called (less - optimized than other allocators). Thus, using it as a generic allocator is - not suggested. - - How does it work: - Memory is allocated in continuous memory areas called chunks by alloc_chunk() - Chunk format: - [ block ][ block ] ... [ block ][ block terminator ] - - All blocks and the block terminator is started with block_header. The block - header contains the size of the previous and the next block. These sizes - can also contain special values. - Block size: - 0 - The block is a free_block, with a different size member. - 1 - The block is a block terminator. - n - The block is used at the moment, and the value contains its size. - Previous block size: - 0 - This is the first block of the memory chunk. - n - The size of the previous block. - - Using these size values we can go forward or backward on the block chain. - The unused blocks are stored in a chain list pointed by free_blocks. This - list is useful if we need to find a suitable memory area when the allocator - is called. - - When a block is freed, the new free block is connected to its adjacent free - blocks if possible. - - [ free block ][ used block ][ free block ] - and "used block" is freed, the three blocks are connected together: - [ one big free block ] -*/ - -/* --------------------------------------------------------------------- */ -/* System (OS) functions */ -/* --------------------------------------------------------------------- */ - -/* 64 KByte. */ -#define CHUNK_SIZE (sljit_uw)0x10000 - -struct chunk_header { - void *executable; -}; - -/* - alloc_chunk / free_chunk : - * allocate executable system memory chunks - * the size is always divisible by CHUNK_SIZE - SLJIT_ALLOCATOR_LOCK / SLJIT_ALLOCATOR_UNLOCK : - * provided as part of sljitUtils - * only the allocator requires this lock, sljit is fully thread safe - as it only uses local variables -*/ - -#ifndef __NetBSD__ -#include <sys/stat.h> -#include <fcntl.h> -#include <stdio.h> -#include <string.h> - -#ifndef O_NOATIME -#define O_NOATIME 0 -#endif - -/* this is a linux extension available since kernel 3.11 */ -#ifndef O_TMPFILE -#define O_TMPFILE 020200000 -#endif - -#ifndef _GNU_SOURCE -char *secure_getenv(const char *name); -int mkostemp(char *template, int flags); -#endif - -static SLJIT_INLINE int create_tempfile(void) -{ - int fd; - char tmp_name[256]; - size_t tmp_name_len = 0; - char *dir; - struct stat st; -#if defined(SLJIT_SINGLE_THREADED) && SLJIT_SINGLE_THREADED - mode_t mode; -#endif - -#ifdef HAVE_MEMFD_CREATE - /* this is a GNU extension, make sure to use -D_GNU_SOURCE */ - fd = memfd_create("sljit", MFD_CLOEXEC); - if (fd != -1) { - fchmod(fd, 0); - return fd; - } -#endif - - dir = secure_getenv("TMPDIR"); - - if (dir) { - tmp_name_len = strlen(dir); - if (tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name)) { - if ((stat(dir, &st) == 0) && S_ISDIR(st.st_mode)) - strcpy(tmp_name, dir); - } - } - -#ifdef P_tmpdir - if (!tmp_name_len) { - tmp_name_len = strlen(P_tmpdir); - if (tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name)) - strcpy(tmp_name, P_tmpdir); - } -#endif - if (!tmp_name_len) { - strcpy(tmp_name, "/tmp"); - tmp_name_len = 4; - } - - SLJIT_ASSERT(tmp_name_len > 0 && tmp_name_len < sizeof(tmp_name)); - - if (tmp_name[tmp_name_len - 1] == '/') - tmp_name[--tmp_name_len] = '\0'; - -#ifdef __linux__ - /* - * the previous trimming might had left an empty string if TMPDIR="/" - * so work around the problem below - */ - fd = open(tmp_name_len ? tmp_name : "/", - O_TMPFILE | O_EXCL | O_RDWR | O_NOATIME | O_CLOEXEC, 0); - if (fd != -1) - return fd; -#endif - - if (tmp_name_len + 7 >= sizeof(tmp_name)) - return -1; - - strcpy(tmp_name + tmp_name_len, "/XXXXXX"); -#if defined(SLJIT_SINGLE_THREADED) && SLJIT_SINGLE_THREADED - mode = umask(0777); -#endif - fd = mkostemp(tmp_name, O_CLOEXEC | O_NOATIME); -#if defined(SLJIT_SINGLE_THREADED) && SLJIT_SINGLE_THREADED - umask(mode); -#else - fchmod(fd, 0); -#endif - - if (fd == -1) - return -1; - - if (unlink(tmp_name)) { - close(fd); - return -1; - } - - return fd; -} - -static SLJIT_INLINE struct chunk_header* alloc_chunk(sljit_uw size) -{ - struct chunk_header *retval; - int fd; - - fd = create_tempfile(); - if (fd == -1) - return NULL; - - if (ftruncate(fd, (off_t)size)) { - close(fd); - return NULL; - } - - retval = (struct chunk_header *)mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); - - if (retval == MAP_FAILED) { - close(fd); - return NULL; - } - - retval->executable = mmap(NULL, size, PROT_READ | PROT_EXEC, MAP_SHARED, fd, 0); - - if (retval->executable == MAP_FAILED) { - munmap((void *)retval, size); - close(fd); - return NULL; - } - - close(fd); - return retval; -} -#else -/* - * MAP_REMAPDUP is a NetBSD extension available sinde 8.0, make sure to - * adjust your feature macros (ex: -D_NETBSD_SOURCE) as needed - */ -static SLJIT_INLINE struct chunk_header* alloc_chunk(sljit_uw size) -{ - struct chunk_header *retval; - - retval = (struct chunk_header *)mmap(NULL, size, - PROT_READ | PROT_WRITE | PROT_MPROTECT(PROT_EXEC), - MAP_ANON | MAP_SHARED, -1, 0); - - if (retval == MAP_FAILED) - return NULL; - - retval->executable = mremap(retval, size, NULL, size, MAP_REMAPDUP); - if (retval->executable == MAP_FAILED) { - munmap((void *)retval, size); - return NULL; - } - - if (mprotect(retval->executable, size, PROT_READ | PROT_EXEC) == -1) { - munmap(retval->executable, size); - munmap((void *)retval, size); - return NULL; - } - - return retval; -} -#endif /* NetBSD */ - -static SLJIT_INLINE void free_chunk(void *chunk, sljit_uw size) -{ - struct chunk_header *header = ((struct chunk_header *)chunk) - 1; - - munmap(header->executable, size); - munmap((void *)header, size); -} - -/* --------------------------------------------------------------------- */ -/* Common functions */ -/* --------------------------------------------------------------------- */ - -#define CHUNK_MASK (~(CHUNK_SIZE - 1)) - -struct block_header { - sljit_uw size; - sljit_uw prev_size; - sljit_sw executable_offset; -}; - -struct free_block { - struct block_header header; - struct free_block *next; - struct free_block *prev; - sljit_uw size; -}; - -#define AS_BLOCK_HEADER(base, offset) \ - ((struct block_header*)(((sljit_u8*)base) + offset)) -#define AS_FREE_BLOCK(base, offset) \ - ((struct free_block*)(((sljit_u8*)base) + offset)) -#define MEM_START(base) ((void*)((base) + 1)) -#define ALIGN_SIZE(size) (((size) + sizeof(struct block_header) + 7u) & ~(sljit_uw)7) - -static struct free_block* free_blocks; -static sljit_uw allocated_size; -static sljit_uw total_size; - -static SLJIT_INLINE void sljit_insert_free_block(struct free_block *free_block, sljit_uw size) -{ - free_block->header.size = 0; - free_block->size = size; - - free_block->next = free_blocks; - free_block->prev = NULL; - if (free_blocks) - free_blocks->prev = free_block; - free_blocks = free_block; -} - -static SLJIT_INLINE void sljit_remove_free_block(struct free_block *free_block) -{ - if (free_block->next) - free_block->next->prev = free_block->prev; - - if (free_block->prev) - free_block->prev->next = free_block->next; - else { - SLJIT_ASSERT(free_blocks == free_block); - free_blocks = free_block->next; - } -} - -SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size) -{ - struct chunk_header *chunk_header; - struct block_header *header; - struct block_header *next_header; - struct free_block *free_block; - sljit_uw chunk_size; - sljit_sw executable_offset; - - SLJIT_ALLOCATOR_LOCK(); - if (size < (64 - sizeof(struct block_header))) - size = (64 - sizeof(struct block_header)); - size = ALIGN_SIZE(size); - - free_block = free_blocks; - while (free_block) { - if (free_block->size >= size) { - chunk_size = free_block->size; - if (chunk_size > size + 64) { - /* We just cut a block from the end of the free block. */ - chunk_size -= size; - free_block->size = chunk_size; - header = AS_BLOCK_HEADER(free_block, chunk_size); - header->prev_size = chunk_size; - header->executable_offset = free_block->header.executable_offset; - AS_BLOCK_HEADER(header, size)->prev_size = size; - } - else { - sljit_remove_free_block(free_block); - header = (struct block_header*)free_block; - size = chunk_size; - } - allocated_size += size; - header->size = size; - SLJIT_ALLOCATOR_UNLOCK(); - return MEM_START(header); - } - free_block = free_block->next; - } - - chunk_size = sizeof(struct chunk_header) + sizeof(struct block_header); - chunk_size = (chunk_size + size + CHUNK_SIZE - 1) & CHUNK_MASK; - - chunk_header = alloc_chunk(chunk_size); - if (!chunk_header) { - SLJIT_ALLOCATOR_UNLOCK(); - return NULL; - } - - executable_offset = (sljit_sw)((sljit_u8*)chunk_header->executable - (sljit_u8*)chunk_header); - - chunk_size -= sizeof(struct chunk_header) + sizeof(struct block_header); - total_size += chunk_size; - - header = (struct block_header *)(chunk_header + 1); - - header->prev_size = 0; - header->executable_offset = executable_offset; - if (chunk_size > size + 64) { - /* Cut the allocated space into a free and a used block. */ - allocated_size += size; - header->size = size; - chunk_size -= size; - - free_block = AS_FREE_BLOCK(header, size); - free_block->header.prev_size = size; - free_block->header.executable_offset = executable_offset; - sljit_insert_free_block(free_block, chunk_size); - next_header = AS_BLOCK_HEADER(free_block, chunk_size); - } - else { - /* All space belongs to this allocation. */ - allocated_size += chunk_size; - header->size = chunk_size; - next_header = AS_BLOCK_HEADER(header, chunk_size); - } - next_header->size = 1; - next_header->prev_size = chunk_size; - next_header->executable_offset = executable_offset; - SLJIT_ALLOCATOR_UNLOCK(); - return MEM_START(header); -} - -SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr) -{ - struct block_header *header; - struct free_block* free_block; - - SLJIT_ALLOCATOR_LOCK(); - header = AS_BLOCK_HEADER(ptr, -(sljit_sw)sizeof(struct block_header)); - header = AS_BLOCK_HEADER(header, -header->executable_offset); - allocated_size -= header->size; - - /* Connecting free blocks together if possible. */ - - /* If header->prev_size == 0, free_block will equal to header. - In this case, free_block->header.size will be > 0. */ - free_block = AS_FREE_BLOCK(header, -(sljit_sw)header->prev_size); - if (SLJIT_UNLIKELY(!free_block->header.size)) { - free_block->size += header->size; - header = AS_BLOCK_HEADER(free_block, free_block->size); - header->prev_size = free_block->size; - } - else { - free_block = (struct free_block*)header; - sljit_insert_free_block(free_block, header->size); - } - - header = AS_BLOCK_HEADER(free_block, free_block->size); - if (SLJIT_UNLIKELY(!header->size)) { - free_block->size += ((struct free_block*)header)->size; - sljit_remove_free_block((struct free_block*)header); - header = AS_BLOCK_HEADER(free_block, free_block->size); - header->prev_size = free_block->size; - } - - /* The whole chunk is free. */ - if (SLJIT_UNLIKELY(!free_block->header.prev_size && header->size == 1)) { - /* If this block is freed, we still have (allocated_size / 2) free space. */ - if (total_size - free_block->size > (allocated_size * 3 / 2)) { - total_size -= free_block->size; - sljit_remove_free_block(free_block); - free_chunk(free_block, free_block->size + - sizeof(struct chunk_header) + - sizeof(struct block_header)); - } - } - - SLJIT_ALLOCATOR_UNLOCK(); -} - -SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void) -{ - struct free_block* free_block; - struct free_block* next_free_block; - - SLJIT_ALLOCATOR_LOCK(); - - free_block = free_blocks; - while (free_block) { - next_free_block = free_block->next; - if (!free_block->header.prev_size && - AS_BLOCK_HEADER(free_block, free_block->size)->size == 1) { - total_size -= free_block->size; - sljit_remove_free_block(free_block); - free_chunk(free_block, free_block->size + - sizeof(struct chunk_header) + - sizeof(struct block_header)); - } - free_block = next_free_block; - } - - SLJIT_ASSERT((total_size && free_blocks) || (!total_size && !free_blocks)); - SLJIT_ALLOCATOR_UNLOCK(); -} - -SLJIT_API_FUNC_ATTRIBUTE sljit_sw sljit_exec_offset(void* ptr) -{ - return ((struct block_header *)(ptr))[-1].executable_offset; -} diff --git a/thirdparty/pcre2/src/sljit/sljitWXExecAllocator.c b/thirdparty/pcre2/src/sljit/sljitWXExecAllocator.c deleted file mode 100644 index 6893813155..0000000000 --- a/thirdparty/pcre2/src/sljit/sljitWXExecAllocator.c +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Stack-less Just-In-Time compiler - * - * Copyright Zoltan Herczeg (hzmester@freemail.hu). All rights reserved. - * - * Redistribution and use in source and binary forms, with or without modification, are - * permitted provided that the following conditions are met: - * - * 1. Redistributions of source code must retain the above copyright notice, this list of - * conditions and the following disclaimer. - * - * 2. Redistributions in binary form must reproduce the above copyright notice, this list - * of conditions and the following disclaimer in the documentation and/or other materials - * provided with the distribution. - * - * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDER(S) AND CONTRIBUTORS ``AS IS'' AND ANY - * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES - * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT - * SHALL THE COPYRIGHT HOLDER(S) OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, - * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED - * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR - * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN - * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN - * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. - */ - -/* - This file contains a simple W^X executable memory allocator for POSIX - like systems and Windows - - In *NIX, MAP_ANON is required (that is considered a feature) so make - sure to set the right availability macros for your system or the code - will fail to build. - - If your system doesn't support mapping of anonymous pages (ex: IRIX) it - is also likely that it doesn't need this allocator and should be using - the standard one instead. - - It allocates a separate map for each code block and may waste a lot of - memory, because whatever was requested, will be rounded up to the page - size (minimum 4KB, but could be even bigger). - - It changes the page permissions (RW <-> RX) as needed and therefore, if you - will be updating the code after it has been generated, need to make sure to - block any concurrent execution, or could result in a SIGBUS, that could - even manifest itself at a different address than the one that was being - modified. - - Only use if you are unable to use the regular allocator because of security - restrictions and adding exceptions to your application or the system are - not possible. -*/ - -#define SLJIT_UPDATE_WX_FLAGS(from, to, enable_exec) \ - sljit_update_wx_flags((from), (to), (enable_exec)) - -#ifndef _WIN32 -#include <sys/types.h> -#include <sys/mman.h> - -#ifdef __NetBSD__ -#define SLJIT_PROT_WX PROT_MPROTECT(PROT_EXEC) -#define check_se_protected(ptr, size) (0) -#else /* POSIX */ -#if !(defined SLJIT_SINGLE_THREADED && SLJIT_SINGLE_THREADED) -#include <pthread.h> -#define SLJIT_SE_LOCK() pthread_mutex_lock(&se_lock) -#define SLJIT_SE_UNLOCK() pthread_mutex_unlock(&se_lock) -#endif /* !SLJIT_SINGLE_THREADED */ - -#define check_se_protected(ptr, size) generic_se_protected(ptr, size) - -static SLJIT_INLINE int generic_se_protected(void *ptr, sljit_uw size) -{ - if (SLJIT_LIKELY(!mprotect(ptr, size, PROT_EXEC))) - return mprotect(ptr, size, PROT_READ | PROT_WRITE); - - return -1; -} -#endif /* NetBSD */ - -#ifndef SLJIT_SE_LOCK -#define SLJIT_SE_LOCK() -#endif -#ifndef SLJIT_SE_UNLOCK -#define SLJIT_SE_UNLOCK() -#endif -#ifndef SLJIT_PROT_WX -#define SLJIT_PROT_WX 0 -#endif - -SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size) -{ -#if !(defined SLJIT_SINGLE_THREADED && SLJIT_SINGLE_THREADED) \ - && !defined(__NetBSD__) - static pthread_mutex_t se_lock = PTHREAD_MUTEX_INITIALIZER; -#endif - static int se_protected = !SLJIT_PROT_WX; - int prot = PROT_READ | PROT_WRITE | SLJIT_PROT_WX; - sljit_uw* ptr; - - if (SLJIT_UNLIKELY(se_protected < 0)) - return NULL; - -#ifdef PROT_MAX - prot |= PROT_MAX(PROT_READ | PROT_WRITE | PROT_EXEC); -#endif - - size += sizeof(sljit_uw); - ptr = (sljit_uw*)mmap(NULL, size, prot, MAP_PRIVATE | MAP_ANON, -1, 0); - - if (ptr == MAP_FAILED) - return NULL; - - if (SLJIT_UNLIKELY(se_protected > 0)) { - SLJIT_SE_LOCK(); - se_protected = check_se_protected(ptr, size); - SLJIT_SE_UNLOCK(); - if (SLJIT_UNLIKELY(se_protected < 0)) { - munmap((void *)ptr, size); - return NULL; - } - } - - *ptr++ = size; - return ptr; -} - -#undef SLJIT_PROT_WX -#undef SLJIT_SE_UNLOCK -#undef SLJIT_SE_LOCK - -SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr) -{ - sljit_uw *start_ptr = ((sljit_uw*)ptr) - 1; - munmap((void*)start_ptr, *start_ptr); -} - -static void sljit_update_wx_flags(void *from, void *to, sljit_s32 enable_exec) -{ - sljit_uw page_mask = (sljit_uw)get_page_alignment(); - sljit_uw start = (sljit_uw)from; - sljit_uw end = (sljit_uw)to; - int prot = PROT_READ | (enable_exec ? PROT_EXEC : PROT_WRITE); - - SLJIT_ASSERT(start < end); - - start &= ~page_mask; - end = (end + page_mask) & ~page_mask; - - mprotect((void*)start, end - start, prot); -} - -#else /* windows */ - -SLJIT_API_FUNC_ATTRIBUTE void* sljit_malloc_exec(sljit_uw size) -{ - sljit_uw *ptr; - - size += sizeof(sljit_uw); - ptr = (sljit_uw*)VirtualAlloc(NULL, size, - MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); - - if (!ptr) - return NULL; - - *ptr++ = size; - - return ptr; -} - -SLJIT_API_FUNC_ATTRIBUTE void sljit_free_exec(void* ptr) -{ - sljit_uw start = (sljit_uw)ptr - sizeof(sljit_uw); -#if defined(SLJIT_DEBUG) && SLJIT_DEBUG - sljit_uw page_mask = (sljit_uw)get_page_alignment(); - - SLJIT_ASSERT(!(start & page_mask)); -#endif - VirtualFree((void*)start, 0, MEM_RELEASE); -} - -static void sljit_update_wx_flags(void *from, void *to, sljit_s32 enable_exec) -{ - DWORD oldprot; - sljit_uw page_mask = (sljit_uw)get_page_alignment(); - sljit_uw start = (sljit_uw)from; - sljit_uw end = (sljit_uw)to; - DWORD prot = enable_exec ? PAGE_EXECUTE : PAGE_READWRITE; - - SLJIT_ASSERT(start < end); - - start &= ~page_mask; - end = (end + page_mask) & ~page_mask; - - VirtualProtect((void*)start, end - start, prot, &oldprot); -} - -#endif /* !windows */ - -SLJIT_API_FUNC_ATTRIBUTE void sljit_free_unused_memory_exec(void) -{ - /* This allocator does not keep unused memory for future allocations. */ -} |