initial commit, 4.5 stable
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
Some checks failed
🔗 GHA / 📊 Static checks (push) Has been cancelled
🔗 GHA / 🤖 Android (push) Has been cancelled
🔗 GHA / 🍏 iOS (push) Has been cancelled
🔗 GHA / 🐧 Linux (push) Has been cancelled
🔗 GHA / 🍎 macOS (push) Has been cancelled
🔗 GHA / 🏁 Windows (push) Has been cancelled
🔗 GHA / 🌐 Web (push) Has been cancelled
This commit is contained in:
6
scene/2d/navigation/SCsub
Normal file
6
scene/2d/navigation/SCsub
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/usr/bin/env python
|
||||
from misc.utility.scons_hints import *
|
||||
|
||||
Import("env")
|
||||
|
||||
env.add_source_files(env.scene_sources, "*.cpp")
|
1138
scene/2d/navigation/navigation_agent_2d.cpp
Normal file
1138
scene/2d/navigation/navigation_agent_2d.cpp
Normal file
File diff suppressed because it is too large
Load Diff
280
scene/2d/navigation/navigation_agent_2d.h
Normal file
280
scene/2d/navigation/navigation_agent_2d.h
Normal file
@@ -0,0 +1,280 @@
|
||||
/**************************************************************************/
|
||||
/* navigation_agent_2d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/main/node.h"
|
||||
#include "servers/navigation/navigation_globals.h"
|
||||
#include "servers/navigation/navigation_path_query_parameters_2d.h"
|
||||
#include "servers/navigation/navigation_path_query_result_2d.h"
|
||||
|
||||
class Node2D;
|
||||
|
||||
class NavigationAgent2D : public Node {
|
||||
GDCLASS(NavigationAgent2D, Node);
|
||||
|
||||
Node2D *agent_parent = nullptr;
|
||||
|
||||
RID agent;
|
||||
RID map_override;
|
||||
|
||||
bool avoidance_enabled = false;
|
||||
uint32_t avoidance_layers = 1;
|
||||
uint32_t avoidance_mask = 1;
|
||||
real_t avoidance_priority = 1.0;
|
||||
uint32_t navigation_layers = 1;
|
||||
NavigationPathQueryParameters2D::PathfindingAlgorithm pathfinding_algorithm = NavigationPathQueryParameters2D::PathfindingAlgorithm::PATHFINDING_ALGORITHM_ASTAR;
|
||||
NavigationPathQueryParameters2D::PathPostProcessing path_postprocessing = NavigationPathQueryParameters2D::PathPostProcessing::PATH_POSTPROCESSING_CORRIDORFUNNEL;
|
||||
BitField<NavigationPathQueryParameters2D::PathMetadataFlags> path_metadata_flags = NavigationPathQueryParameters2D::PathMetadataFlags::PATH_METADATA_INCLUDE_ALL;
|
||||
|
||||
real_t path_desired_distance = 20.0;
|
||||
real_t target_desired_distance = 10.0;
|
||||
real_t radius = NavigationDefaults2D::AVOIDANCE_AGENT_RADIUS;
|
||||
real_t neighbor_distance = NavigationDefaults2D::AVOIDANCE_AGENT_NEIGHBOR_DISTANCE;
|
||||
int max_neighbors = NavigationDefaults2D::AVOIDANCE_AGENT_MAX_NEIGHBORS;
|
||||
real_t time_horizon_agents = NavigationDefaults2D::AVOIDANCE_AGENT_TIME_HORIZON_AGENTS;
|
||||
real_t time_horizon_obstacles = NavigationDefaults2D::AVOIDANCE_AGENT_TIME_HORIZON_OBSTACLES;
|
||||
real_t max_speed = NavigationDefaults2D::AVOIDANCE_AGENT_MAX_SPEED;
|
||||
real_t path_max_distance = 100.0;
|
||||
bool simplify_path = false;
|
||||
real_t simplify_epsilon = 0.0;
|
||||
float path_return_max_length = 0.0;
|
||||
float path_return_max_radius = 0.0;
|
||||
int path_search_max_polygons = NavigationDefaults2D::path_search_max_polygons;
|
||||
float path_search_max_distance = 0.0;
|
||||
|
||||
Vector2 target_position;
|
||||
|
||||
Ref<NavigationPathQueryParameters2D> navigation_query;
|
||||
Ref<NavigationPathQueryResult2D> navigation_result;
|
||||
int navigation_path_index = 0;
|
||||
|
||||
// the velocity result of the avoidance simulation step
|
||||
Vector2 safe_velocity;
|
||||
|
||||
/// The submitted target velocity, sets the "wanted" rvo agent velocity on the next update
|
||||
// this velocity is not guaranteed, the simulation will try to fulfill it if possible
|
||||
// if other agents or obstacles interfere it will be changed accordingly
|
||||
Vector2 velocity;
|
||||
bool velocity_submitted = false;
|
||||
|
||||
/// The submitted forced velocity, overrides the rvo agent velocity on the next update
|
||||
// should only be used very intentionally and not every frame as it interferes with the simulation stability
|
||||
Vector2 velocity_forced;
|
||||
bool velocity_forced_submitted = false;
|
||||
|
||||
bool target_position_submitted = false;
|
||||
|
||||
bool target_reached = false;
|
||||
bool navigation_finished = true;
|
||||
bool last_waypoint_reached = false;
|
||||
|
||||
// Debug properties for exposed bindings
|
||||
bool debug_enabled = false;
|
||||
float debug_path_custom_point_size = 4.0;
|
||||
float debug_path_custom_line_width = -1.0;
|
||||
bool debug_use_custom = false;
|
||||
Color debug_path_custom_color = Color(1.0, 1.0, 1.0, 1.0);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
// Debug properties internal only
|
||||
bool debug_path_dirty = true;
|
||||
RID debug_path_instance;
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
public:
|
||||
NavigationAgent2D();
|
||||
virtual ~NavigationAgent2D();
|
||||
|
||||
RID get_rid() const { return agent; }
|
||||
|
||||
void set_avoidance_enabled(bool p_enabled);
|
||||
bool get_avoidance_enabled() const;
|
||||
|
||||
void set_agent_parent(Node *p_agent_parent);
|
||||
|
||||
void set_navigation_layers(uint32_t p_navigation_layers);
|
||||
uint32_t get_navigation_layers() const;
|
||||
|
||||
void set_navigation_layer_value(int p_layer_number, bool p_value);
|
||||
bool get_navigation_layer_value(int p_layer_number) const;
|
||||
|
||||
void set_pathfinding_algorithm(const NavigationPathQueryParameters2D::PathfindingAlgorithm p_pathfinding_algorithm);
|
||||
NavigationPathQueryParameters2D::PathfindingAlgorithm get_pathfinding_algorithm() const {
|
||||
return pathfinding_algorithm;
|
||||
}
|
||||
|
||||
void set_path_postprocessing(const NavigationPathQueryParameters2D::PathPostProcessing p_path_postprocessing);
|
||||
NavigationPathQueryParameters2D::PathPostProcessing get_path_postprocessing() const {
|
||||
return path_postprocessing;
|
||||
}
|
||||
|
||||
void set_path_metadata_flags(BitField<NavigationPathQueryParameters2D::PathMetadataFlags> p_flags);
|
||||
BitField<NavigationPathQueryParameters2D::PathMetadataFlags> get_path_metadata_flags() const {
|
||||
return path_metadata_flags;
|
||||
}
|
||||
|
||||
void set_navigation_map(RID p_navigation_map);
|
||||
RID get_navigation_map() const;
|
||||
|
||||
void set_path_desired_distance(real_t p_dd);
|
||||
real_t get_path_desired_distance() const { return path_desired_distance; }
|
||||
|
||||
void set_target_desired_distance(real_t p_dd);
|
||||
real_t get_target_desired_distance() const { return target_desired_distance; }
|
||||
|
||||
void set_radius(real_t p_radius);
|
||||
real_t get_radius() const { return radius; }
|
||||
|
||||
void set_neighbor_distance(real_t p_distance);
|
||||
real_t get_neighbor_distance() const { return neighbor_distance; }
|
||||
|
||||
void set_max_neighbors(int p_count);
|
||||
int get_max_neighbors() const { return max_neighbors; }
|
||||
|
||||
void set_time_horizon_agents(real_t p_time_horizon);
|
||||
real_t get_time_horizon_agents() const { return time_horizon_agents; }
|
||||
|
||||
void set_time_horizon_obstacles(real_t p_time_horizon);
|
||||
real_t get_time_horizon_obstacles() const { return time_horizon_obstacles; }
|
||||
|
||||
void set_max_speed(real_t p_max_speed);
|
||||
real_t get_max_speed() const { return max_speed; }
|
||||
|
||||
void set_path_max_distance(real_t p_pmd);
|
||||
real_t get_path_max_distance();
|
||||
|
||||
void set_target_position(Vector2 p_position);
|
||||
Vector2 get_target_position() const;
|
||||
|
||||
void set_simplify_path(bool p_enabled);
|
||||
bool get_simplify_path() const;
|
||||
|
||||
void set_simplify_epsilon(real_t p_epsilon);
|
||||
real_t get_simplify_epsilon() const;
|
||||
|
||||
void set_path_return_max_length(float p_length);
|
||||
float get_path_return_max_length() const;
|
||||
|
||||
void set_path_return_max_radius(float p_radius);
|
||||
float get_path_return_max_radius() const;
|
||||
|
||||
void set_path_search_max_polygons(int p_max_polygons);
|
||||
int get_path_search_max_polygons() const;
|
||||
|
||||
void set_path_search_max_distance(float p_distance);
|
||||
float get_path_search_max_distance() const;
|
||||
|
||||
float get_path_length() const;
|
||||
|
||||
Vector2 get_next_path_position();
|
||||
|
||||
Ref<NavigationPathQueryResult2D> get_current_navigation_result() const { return navigation_result; }
|
||||
|
||||
const Vector<Vector2> &get_current_navigation_path() const { return navigation_result->get_path(); }
|
||||
|
||||
int get_current_navigation_path_index() const { return navigation_path_index; }
|
||||
|
||||
real_t distance_to_target() const;
|
||||
bool is_target_reached() const;
|
||||
bool is_target_reachable();
|
||||
bool is_navigation_finished();
|
||||
Vector2 get_final_position();
|
||||
|
||||
void set_velocity(const Vector2 p_velocity);
|
||||
Vector2 get_velocity() { return velocity; }
|
||||
|
||||
void set_velocity_forced(const Vector2 p_velocity);
|
||||
|
||||
void _avoidance_done(Vector2 p_new_velocity);
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
void set_avoidance_layers(uint32_t p_layers);
|
||||
uint32_t get_avoidance_layers() const;
|
||||
|
||||
void set_avoidance_mask(uint32_t p_mask);
|
||||
uint32_t get_avoidance_mask() const;
|
||||
|
||||
void set_avoidance_layer_value(int p_layer_number, bool p_value);
|
||||
bool get_avoidance_layer_value(int p_layer_number) const;
|
||||
|
||||
void set_avoidance_mask_value(int p_mask_number, bool p_value);
|
||||
bool get_avoidance_mask_value(int p_mask_number) const;
|
||||
|
||||
void set_avoidance_priority(real_t p_priority);
|
||||
real_t get_avoidance_priority() const;
|
||||
|
||||
void set_debug_enabled(bool p_enabled);
|
||||
bool get_debug_enabled() const;
|
||||
|
||||
void set_debug_use_custom(bool p_enabled);
|
||||
bool get_debug_use_custom() const;
|
||||
|
||||
void set_debug_path_custom_color(Color p_color);
|
||||
Color get_debug_path_custom_color() const;
|
||||
|
||||
void set_debug_path_custom_point_size(float p_point_size);
|
||||
float get_debug_path_custom_point_size() const;
|
||||
|
||||
void set_debug_path_custom_line_width(float p_line_width);
|
||||
float get_debug_path_custom_line_width() const;
|
||||
|
||||
private:
|
||||
bool _is_target_reachable() const;
|
||||
Vector2 _get_final_position() const;
|
||||
|
||||
void _update_navigation();
|
||||
void _advance_waypoints(const Vector2 &p_origin);
|
||||
void _request_repath();
|
||||
|
||||
bool _is_last_waypoint() const;
|
||||
void _move_to_next_waypoint();
|
||||
bool _is_within_waypoint_distance(const Vector2 &p_origin) const;
|
||||
bool _is_within_target_distance(const Vector2 &p_origin) const;
|
||||
|
||||
void _trigger_waypoint_reached();
|
||||
void _transition_to_navigation_finished();
|
||||
void _transition_to_target_reached();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void _navigation_debug_changed();
|
||||
void _update_debug_path();
|
||||
#endif // DEBUG_ENABLED
|
||||
};
|
445
scene/2d/navigation/navigation_link_2d.cpp
Normal file
445
scene/2d/navigation/navigation_link_2d.cpp
Normal file
@@ -0,0 +1,445 @@
|
||||
/**************************************************************************/
|
||||
/* navigation_link_2d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "navigation_link_2d.h"
|
||||
|
||||
#include "core/math/geometry_2d.h"
|
||||
#include "scene/resources/world_2d.h"
|
||||
#include "servers/navigation_server_2d.h"
|
||||
|
||||
void NavigationLink2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationLink2D::get_rid);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationLink2D::set_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationLink2D::is_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationLink2D::set_navigation_map);
|
||||
ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationLink2D::get_navigation_map);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_bidirectional", "bidirectional"), &NavigationLink2D::set_bidirectional);
|
||||
ClassDB::bind_method(D_METHOD("is_bidirectional"), &NavigationLink2D::is_bidirectional);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationLink2D::set_navigation_layers);
|
||||
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationLink2D::get_navigation_layers);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationLink2D::set_navigation_layer_value);
|
||||
ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationLink2D::get_navigation_layer_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_start_position", "position"), &NavigationLink2D::set_start_position);
|
||||
ClassDB::bind_method(D_METHOD("get_start_position"), &NavigationLink2D::get_start_position);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_end_position", "position"), &NavigationLink2D::set_end_position);
|
||||
ClassDB::bind_method(D_METHOD("get_end_position"), &NavigationLink2D::get_end_position);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_global_start_position", "position"), &NavigationLink2D::set_global_start_position);
|
||||
ClassDB::bind_method(D_METHOD("get_global_start_position"), &NavigationLink2D::get_global_start_position);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_global_end_position", "position"), &NavigationLink2D::set_global_end_position);
|
||||
ClassDB::bind_method(D_METHOD("get_global_end_position"), &NavigationLink2D::get_global_end_position);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationLink2D::set_enter_cost);
|
||||
ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationLink2D::get_enter_cost);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationLink2D::set_travel_cost);
|
||||
ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationLink2D::get_travel_cost);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "bidirectional"), "set_bidirectional", "is_bidirectional");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "start_position"), "set_start_position", "get_start_position");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "end_position"), "set_end_position", "get_end_position");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost");
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool NavigationLink2D::_set(const StringName &p_name, const Variant &p_value) {
|
||||
if (p_name == "start_location") {
|
||||
set_start_position(p_value);
|
||||
return true;
|
||||
}
|
||||
if (p_name == "end_location") {
|
||||
set_end_position(p_value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NavigationLink2D::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
if (p_name == "start_location") {
|
||||
r_ret = get_start_position();
|
||||
return true;
|
||||
}
|
||||
if (p_name == "end_location") {
|
||||
r_ret = get_end_position();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
void NavigationLink2D::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
_link_enter_navigation_map();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_TRANSFORM_CHANGED: {
|
||||
set_physics_process_internal(true);
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
|
||||
set_physics_process_internal(false);
|
||||
_link_update_transform();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
_link_exit_navigation_map();
|
||||
} break;
|
||||
case NOTIFICATION_DRAW: {
|
||||
#ifdef DEBUG_ENABLED
|
||||
_update_debug_mesh();
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
Rect2 NavigationLink2D::_edit_get_rect() const {
|
||||
if (!is_inside_tree()) {
|
||||
return Rect2();
|
||||
}
|
||||
|
||||
real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map());
|
||||
|
||||
Rect2 rect(get_start_position(), Size2());
|
||||
rect.expand_to(get_end_position());
|
||||
rect.grow_by(radius);
|
||||
return rect;
|
||||
}
|
||||
|
||||
bool NavigationLink2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
|
||||
Vector2 closest_point = Geometry2D::get_closest_point_to_segment(p_point, get_start_position(), get_end_position());
|
||||
return p_point.distance_to(closest_point) < p_tolerance;
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
RID NavigationLink2D::get_rid() const {
|
||||
return link;
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_enabled(bool p_enabled) {
|
||||
if (enabled == p_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabled = p_enabled;
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_enabled(link, enabled);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_navigation_map(RID p_navigation_map) {
|
||||
if (map_override == p_navigation_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
map_override = p_navigation_map;
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_map(link, map_override);
|
||||
}
|
||||
|
||||
RID NavigationLink2D::get_navigation_map() const {
|
||||
if (map_override.is_valid()) {
|
||||
return map_override;
|
||||
} else if (is_inside_tree()) {
|
||||
return get_world_2d()->get_navigation_map();
|
||||
}
|
||||
return RID();
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_bidirectional(bool p_bidirectional) {
|
||||
if (bidirectional == p_bidirectional) {
|
||||
return;
|
||||
}
|
||||
|
||||
bidirectional = p_bidirectional;
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_bidirectional(link, bidirectional);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_navigation_layers(uint32_t p_navigation_layers) {
|
||||
if (navigation_layers == p_navigation_layers) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigation_layers = p_navigation_layers;
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_navigation_layers(link, navigation_layers);
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_navigation_layer_value(int p_layer_number, bool p_value) {
|
||||
ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive.");
|
||||
ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive.");
|
||||
|
||||
uint32_t _navigation_layers = get_navigation_layers();
|
||||
|
||||
if (p_value) {
|
||||
_navigation_layers |= 1 << (p_layer_number - 1);
|
||||
} else {
|
||||
_navigation_layers &= ~(1 << (p_layer_number - 1));
|
||||
}
|
||||
|
||||
set_navigation_layers(_navigation_layers);
|
||||
}
|
||||
|
||||
bool NavigationLink2D::get_navigation_layer_value(int p_layer_number) const {
|
||||
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive.");
|
||||
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive.");
|
||||
|
||||
return get_navigation_layers() & (1 << (p_layer_number - 1));
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_start_position(Vector2 p_position) {
|
||||
if (start_position.is_equal_approx(p_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
start_position = p_position;
|
||||
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
|
||||
|
||||
update_configuration_warnings();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_end_position(Vector2 p_position) {
|
||||
if (end_position.is_equal_approx(p_position)) {
|
||||
return;
|
||||
}
|
||||
|
||||
end_position = p_position;
|
||||
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
|
||||
|
||||
update_configuration_warnings();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_global_start_position(Vector2 p_position) {
|
||||
if (is_inside_tree()) {
|
||||
set_start_position(to_local(p_position));
|
||||
} else {
|
||||
set_start_position(p_position);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 NavigationLink2D::get_global_start_position() const {
|
||||
if (is_inside_tree()) {
|
||||
return to_global(start_position);
|
||||
} else {
|
||||
return start_position;
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_global_end_position(Vector2 p_position) {
|
||||
if (is_inside_tree()) {
|
||||
set_end_position(to_local(p_position));
|
||||
} else {
|
||||
set_end_position(p_position);
|
||||
}
|
||||
}
|
||||
|
||||
Vector2 NavigationLink2D::get_global_end_position() const {
|
||||
if (is_inside_tree()) {
|
||||
return to_global(end_position);
|
||||
} else {
|
||||
return end_position;
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_enter_cost(real_t p_enter_cost) {
|
||||
ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive.");
|
||||
if (Math::is_equal_approx(enter_cost, p_enter_cost)) {
|
||||
return;
|
||||
}
|
||||
|
||||
enter_cost = p_enter_cost;
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_enter_cost(link, enter_cost);
|
||||
}
|
||||
|
||||
void NavigationLink2D::set_travel_cost(real_t p_travel_cost) {
|
||||
ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive.");
|
||||
if (Math::is_equal_approx(travel_cost, p_travel_cost)) {
|
||||
return;
|
||||
}
|
||||
|
||||
travel_cost = p_travel_cost;
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_travel_cost(link, travel_cost);
|
||||
}
|
||||
|
||||
PackedStringArray NavigationLink2D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node2D::get_configuration_warnings();
|
||||
|
||||
if (start_position.is_equal_approx(end_position)) {
|
||||
warnings.push_back(RTR("NavigationLink2D start position should be different than the end position to be useful."));
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void NavigationLink2D::_link_enter_navigation_map() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (map_override.is_valid()) {
|
||||
NavigationServer2D::get_singleton()->link_set_map(link, map_override);
|
||||
} else {
|
||||
NavigationServer2D::get_singleton()->link_set_map(link, get_world_2d()->get_navigation_map());
|
||||
}
|
||||
|
||||
current_global_transform = get_global_transform();
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
|
||||
NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
|
||||
NavigationServer2D::get_singleton()->link_set_enabled(link, enabled);
|
||||
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void NavigationLink2D::_link_exit_navigation_map() {
|
||||
NavigationServer2D::get_singleton()->link_set_map(link, RID());
|
||||
}
|
||||
|
||||
void NavigationLink2D::_link_update_transform() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Transform2D new_global_transform = get_global_transform();
|
||||
if (current_global_transform != new_global_transform) {
|
||||
current_global_transform = new_global_transform;
|
||||
NavigationServer2D::get_singleton()->link_set_start_position(link, current_global_transform.xform(start_position));
|
||||
NavigationServer2D::get_singleton()->link_set_end_position(link, current_global_transform.xform(end_position));
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationLink2D::_update_debug_mesh() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!Engine::get_singleton()->is_editor_hint() && !NavigationServer2D::get_singleton()->get_debug_enabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Color color;
|
||||
if (enabled) {
|
||||
color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_color();
|
||||
} else {
|
||||
color = NavigationServer2D::get_singleton()->get_debug_navigation_link_connection_disabled_color();
|
||||
}
|
||||
|
||||
real_t radius = NavigationServer2D::get_singleton()->map_get_link_connection_radius(get_world_2d()->get_navigation_map());
|
||||
|
||||
draw_line(get_start_position(), get_end_position(), color);
|
||||
draw_arc(get_start_position(), radius, 0, Math::TAU, 10, color);
|
||||
draw_arc(get_end_position(), radius, 0, Math::TAU, 10, color);
|
||||
|
||||
const Vector2 link_segment = end_position - start_position;
|
||||
const float arror_len = 5.0;
|
||||
|
||||
{
|
||||
Vector2 anchor = start_position + (link_segment * 0.75);
|
||||
Vector2 direction = start_position.direction_to(end_position);
|
||||
Vector2 arrow_dir = -direction.orthogonal();
|
||||
draw_line(anchor, anchor + (arrow_dir - direction) * arror_len, color);
|
||||
|
||||
arrow_dir = direction.orthogonal();
|
||||
draw_line(anchor, anchor + (arrow_dir - direction) * arror_len, color);
|
||||
}
|
||||
|
||||
if (is_bidirectional()) {
|
||||
Vector2 anchor = start_position + (link_segment * 0.25);
|
||||
Vector2 direction = end_position.direction_to(start_position);
|
||||
Vector2 arrow_dir = -direction.orthogonal();
|
||||
draw_line(anchor, anchor + (arrow_dir - direction) * arror_len, color);
|
||||
|
||||
arrow_dir = direction.orthogonal();
|
||||
draw_line(anchor, anchor + (arrow_dir - direction) * arror_len, color);
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
NavigationLink2D::NavigationLink2D() {
|
||||
link = NavigationServer2D::get_singleton()->link_create();
|
||||
|
||||
NavigationServer2D::get_singleton()->link_set_owner_id(link, get_instance_id());
|
||||
NavigationServer2D::get_singleton()->link_set_enter_cost(link, enter_cost);
|
||||
NavigationServer2D::get_singleton()->link_set_travel_cost(link, travel_cost);
|
||||
NavigationServer2D::get_singleton()->link_set_navigation_layers(link, navigation_layers);
|
||||
NavigationServer2D::get_singleton()->link_set_bidirectional(link, bidirectional);
|
||||
NavigationServer2D::get_singleton()->link_set_enabled(link, enabled);
|
||||
|
||||
set_notify_transform(true);
|
||||
set_hide_clip_children(true);
|
||||
}
|
||||
|
||||
NavigationLink2D::~NavigationLink2D() {
|
||||
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
|
||||
NavigationServer2D::get_singleton()->free(link);
|
||||
link = RID();
|
||||
}
|
112
scene/2d/navigation/navigation_link_2d.h
Normal file
112
scene/2d/navigation/navigation_link_2d.h
Normal file
@@ -0,0 +1,112 @@
|
||||
/**************************************************************************/
|
||||
/* navigation_link_2d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/2d/node_2d.h"
|
||||
|
||||
class NavigationLink2D : public Node2D {
|
||||
GDCLASS(NavigationLink2D, Node2D);
|
||||
|
||||
bool enabled = true;
|
||||
RID link;
|
||||
RID map_override;
|
||||
bool bidirectional = true;
|
||||
uint32_t navigation_layers = 1;
|
||||
Vector2 end_position;
|
||||
Vector2 start_position;
|
||||
real_t enter_cost = 0.0;
|
||||
real_t travel_cost = 1.0;
|
||||
|
||||
Transform2D current_global_transform;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void _update_debug_mesh();
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual Rect2 _edit_get_rect() const override;
|
||||
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
|
||||
#endif // DEBUG_ENABLED
|
||||
RID get_rid() const;
|
||||
|
||||
void set_enabled(bool p_enabled);
|
||||
bool is_enabled() const { return enabled; }
|
||||
|
||||
void set_navigation_map(RID p_navigation_map);
|
||||
RID get_navigation_map() const;
|
||||
|
||||
void set_bidirectional(bool p_bidirectional);
|
||||
bool is_bidirectional() const { return bidirectional; }
|
||||
|
||||
void set_navigation_layers(uint32_t p_navigation_layers);
|
||||
uint32_t get_navigation_layers() const { return navigation_layers; }
|
||||
|
||||
void set_navigation_layer_value(int p_layer_number, bool p_value);
|
||||
bool get_navigation_layer_value(int p_layer_number) const;
|
||||
|
||||
void set_start_position(Vector2 p_position);
|
||||
Vector2 get_start_position() const { return start_position; }
|
||||
|
||||
void set_end_position(Vector2 p_position);
|
||||
Vector2 get_end_position() const { return end_position; }
|
||||
|
||||
void set_global_start_position(Vector2 p_position);
|
||||
Vector2 get_global_start_position() const;
|
||||
|
||||
void set_global_end_position(Vector2 p_position);
|
||||
Vector2 get_global_end_position() const;
|
||||
|
||||
void set_enter_cost(real_t p_enter_cost);
|
||||
real_t get_enter_cost() const { return enter_cost; }
|
||||
|
||||
void set_travel_cost(real_t p_travel_cost);
|
||||
real_t get_travel_cost() const { return travel_cost; }
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
NavigationLink2D();
|
||||
~NavigationLink2D();
|
||||
|
||||
private:
|
||||
void _link_enter_navigation_map();
|
||||
void _link_exit_navigation_map();
|
||||
void _link_update_transform();
|
||||
};
|
512
scene/2d/navigation/navigation_obstacle_2d.cpp
Normal file
512
scene/2d/navigation/navigation_obstacle_2d.cpp
Normal file
@@ -0,0 +1,512 @@
|
||||
/**************************************************************************/
|
||||
/* navigation_obstacle_2d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "navigation_obstacle_2d.h"
|
||||
|
||||
#include "core/math/geometry_2d.h"
|
||||
#include "scene/resources/2d/navigation_mesh_source_geometry_data_2d.h"
|
||||
#include "scene/resources/2d/navigation_polygon.h"
|
||||
#include "scene/resources/world_2d.h"
|
||||
#include "servers/navigation_server_2d.h"
|
||||
|
||||
Callable NavigationObstacle2D::_navmesh_source_geometry_parsing_callback;
|
||||
RID NavigationObstacle2D::_navmesh_source_geometry_parser;
|
||||
|
||||
void NavigationObstacle2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationObstacle2D::get_rid);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_avoidance_enabled", "enabled"), &NavigationObstacle2D::set_avoidance_enabled);
|
||||
ClassDB::bind_method(D_METHOD("get_avoidance_enabled"), &NavigationObstacle2D::get_avoidance_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationObstacle2D::set_navigation_map);
|
||||
ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationObstacle2D::get_navigation_map);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_radius", "radius"), &NavigationObstacle2D::set_radius);
|
||||
ClassDB::bind_method(D_METHOD("get_radius"), &NavigationObstacle2D::get_radius);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_velocity", "velocity"), &NavigationObstacle2D::set_velocity);
|
||||
ClassDB::bind_method(D_METHOD("get_velocity"), &NavigationObstacle2D::get_velocity);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_vertices", "vertices"), &NavigationObstacle2D::set_vertices);
|
||||
ClassDB::bind_method(D_METHOD("get_vertices"), &NavigationObstacle2D::get_vertices);
|
||||
|
||||
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::INT, "avoidance_layers", PROPERTY_HINT_LAYERS_AVOIDANCE), "set_avoidance_layers", "get_avoidance_layers");
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_POST_ENTER_TREE: {
|
||||
if (map_override.is_valid()) {
|
||||
_update_map(map_override);
|
||||
} else if (is_inside_tree()) {
|
||||
_update_map(get_world_2d()->get_navigation_map());
|
||||
} else {
|
||||
_update_map(RID());
|
||||
}
|
||||
previous_transform = get_global_transform();
|
||||
// need to trigger map controlled agent assignment somehow for the fake_agent since obstacles use no callback like regular agents
|
||||
NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
|
||||
_update_transform();
|
||||
set_physics_process_internal(true);
|
||||
#ifdef DEBUG_ENABLED
|
||||
RS::get_singleton()->canvas_item_set_parent(debug_canvas_item, get_world_2d()->get_canvas());
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
set_physics_process_internal(false);
|
||||
_update_map(RID());
|
||||
#ifdef DEBUG_ENABLED
|
||||
RS::get_singleton()->canvas_item_set_parent(debug_canvas_item, RID());
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_SUSPENDED:
|
||||
case NOTIFICATION_PAUSED: {
|
||||
if (!can_process()) {
|
||||
map_before_pause = map_current;
|
||||
_update_map(RID());
|
||||
} else if (can_process() && !(map_before_pause == RID())) {
|
||||
_update_map(map_before_pause);
|
||||
map_before_pause = RID();
|
||||
}
|
||||
NavigationServer2D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_UNSUSPENDED: {
|
||||
if (get_tree()->is_paused()) {
|
||||
break;
|
||||
}
|
||||
[[fallthrough]];
|
||||
}
|
||||
|
||||
case NOTIFICATION_UNPAUSED: {
|
||||
if (!can_process()) {
|
||||
map_before_pause = map_current;
|
||||
_update_map(RID());
|
||||
} else if (can_process() && !(map_before_pause == RID())) {
|
||||
_update_map(map_before_pause);
|
||||
map_before_pause = RID();
|
||||
}
|
||||
NavigationServer2D::get_singleton()->obstacle_set_paused(obstacle, !can_process());
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
#ifdef DEBUG_ENABLED
|
||||
RS::get_singleton()->canvas_item_set_visible(debug_canvas_item, is_visible_in_tree());
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
|
||||
if (is_inside_tree()) {
|
||||
_update_transform();
|
||||
|
||||
if (velocity_submitted) {
|
||||
velocity_submitted = false;
|
||||
// only update if there is a noticeable change, else the rvo agent preferred velocity stays the same
|
||||
if (!previous_velocity.is_equal_approx(velocity)) {
|
||||
NavigationServer2D::get_singleton()->obstacle_set_velocity(obstacle, velocity);
|
||||
}
|
||||
previous_velocity = velocity;
|
||||
}
|
||||
}
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_DRAW: {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (is_inside_tree()) {
|
||||
bool is_debug_enabled = false;
|
||||
if (Engine::get_singleton()->is_editor_hint()) {
|
||||
is_debug_enabled = true;
|
||||
} else if (NavigationServer2D::get_singleton()->get_debug_enabled() && NavigationServer2D::get_singleton()->get_debug_avoidance_enabled()) {
|
||||
is_debug_enabled = true;
|
||||
}
|
||||
|
||||
if (is_debug_enabled) {
|
||||
RS::get_singleton()->canvas_item_clear(debug_canvas_item);
|
||||
RS::get_singleton()->canvas_item_set_transform(debug_canvas_item, Transform2D());
|
||||
_update_fake_agent_radius_debug();
|
||||
_update_static_obstacle_debug();
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
NavigationObstacle2D::NavigationObstacle2D() {
|
||||
obstacle = NavigationServer2D::get_singleton()->obstacle_create();
|
||||
|
||||
NavigationServer2D::get_singleton()->obstacle_set_radius(obstacle, radius);
|
||||
NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle, vertices);
|
||||
NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
|
||||
NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
debug_canvas_item = RenderingServer::get_singleton()->canvas_item_create();
|
||||
debug_mesh_rid = RenderingServer::get_singleton()->mesh_create();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
NavigationObstacle2D::~NavigationObstacle2D() {
|
||||
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
|
||||
|
||||
NavigationServer2D::get_singleton()->free(obstacle);
|
||||
obstacle = RID();
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (debug_mesh_rid.is_valid()) {
|
||||
RenderingServer::get_singleton()->free(debug_mesh_rid);
|
||||
debug_mesh_rid = RID();
|
||||
}
|
||||
if (debug_canvas_item.is_valid()) {
|
||||
RenderingServer::get_singleton()->free(debug_canvas_item);
|
||||
debug_canvas_item = RID();
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::set_vertices(const Vector<Vector2> &p_vertices) {
|
||||
vertices = p_vertices;
|
||||
|
||||
vertices_are_clockwise = !Geometry2D::is_polygon_clockwise(vertices); // Geometry2D is inverted.
|
||||
vertices_are_valid = !Geometry2D::triangulate_polygon(vertices).is_empty();
|
||||
|
||||
const Transform2D node_transform = is_inside_tree() ? get_global_transform() : Transform2D();
|
||||
NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle, node_transform.xform(vertices));
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::set_navigation_map(RID p_navigation_map) {
|
||||
if (map_override == p_navigation_map) {
|
||||
return;
|
||||
}
|
||||
map_override = p_navigation_map;
|
||||
_update_map(map_override);
|
||||
}
|
||||
|
||||
RID NavigationObstacle2D::get_navigation_map() const {
|
||||
if (map_override.is_valid()) {
|
||||
return map_override;
|
||||
} else if (is_inside_tree()) {
|
||||
return get_world_2d()->get_navigation_map();
|
||||
}
|
||||
return RID();
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::set_radius(real_t p_radius) {
|
||||
ERR_FAIL_COND_MSG(p_radius < 0.0, "Radius must be positive.");
|
||||
if (Math::is_equal_approx(radius, p_radius)) {
|
||||
return;
|
||||
}
|
||||
|
||||
radius = p_radius;
|
||||
|
||||
const Vector2 safe_scale = (is_inside_tree() ? get_global_scale() : get_scale()).abs().maxf(0.001);
|
||||
NavigationServer2D::get_singleton()->obstacle_set_radius(obstacle, safe_scale[safe_scale.max_axis_index()] * radius);
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::set_avoidance_layers(uint32_t p_layers) {
|
||||
if (avoidance_layers == p_layers) {
|
||||
return;
|
||||
}
|
||||
avoidance_layers = p_layers;
|
||||
NavigationServer2D::get_singleton()->obstacle_set_avoidance_layers(obstacle, avoidance_layers);
|
||||
}
|
||||
|
||||
uint32_t NavigationObstacle2D::get_avoidance_layers() const {
|
||||
return avoidance_layers;
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::set_avoidance_layer_value(int p_layer_number, bool p_value) {
|
||||
ERR_FAIL_COND_MSG(p_layer_number < 1, "Avoidance layer number must be between 1 and 32 inclusive.");
|
||||
ERR_FAIL_COND_MSG(p_layer_number > 32, "Avoidance layer number must be between 1 and 32 inclusive.");
|
||||
uint32_t avoidance_layers_new = get_avoidance_layers();
|
||||
if (p_value) {
|
||||
avoidance_layers_new |= 1 << (p_layer_number - 1);
|
||||
} else {
|
||||
avoidance_layers_new &= ~(1 << (p_layer_number - 1));
|
||||
}
|
||||
set_avoidance_layers(avoidance_layers_new);
|
||||
}
|
||||
|
||||
bool NavigationObstacle2D::get_avoidance_layer_value(int p_layer_number) const {
|
||||
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Avoidance layer number must be between 1 and 32 inclusive.");
|
||||
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Avoidance layer number must be between 1 and 32 inclusive.");
|
||||
return get_avoidance_layers() & (1 << (p_layer_number - 1));
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::set_avoidance_enabled(bool p_enabled) {
|
||||
if (avoidance_enabled == p_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
avoidance_enabled = p_enabled;
|
||||
NavigationServer2D::get_singleton()->obstacle_set_avoidance_enabled(obstacle, avoidance_enabled);
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
bool NavigationObstacle2D::get_avoidance_enabled() const {
|
||||
return avoidance_enabled;
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::set_velocity(const Vector2 p_velocity) {
|
||||
velocity = 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;
|
||||
}
|
||||
|
||||
PackedStringArray NavigationObstacle2D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node2D::get_configuration_warnings();
|
||||
|
||||
const Vector2 global_scale = get_global_scale();
|
||||
if (global_scale.x < 0.001 || global_scale.y < 0.001) {
|
||||
warnings.push_back(RTR("NavigationObstacle2D does not support negative or zero scaling."));
|
||||
}
|
||||
|
||||
if (radius > 0.0 && !get_global_transform().is_conformal()) {
|
||||
warnings.push_back(RTR("The agent radius can only be scaled uniformly. The largest value along the two axes of the global scale will be used to scale the radius. This value may change in unexpected ways when the node is rotated."));
|
||||
}
|
||||
|
||||
if (radius > 0.0 && get_global_skew() != 0.0) {
|
||||
warnings.push_back(RTR("Skew has no effect on the agent radius."));
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::navmesh_parse_init() {
|
||||
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
|
||||
if (!_navmesh_source_geometry_parser.is_valid()) {
|
||||
_navmesh_source_geometry_parsing_callback = callable_mp_static(&NavigationObstacle2D::navmesh_parse_source_geometry);
|
||||
_navmesh_source_geometry_parser = NavigationServer2D::get_singleton()->source_geometry_parser_create();
|
||||
NavigationServer2D::get_singleton()->source_geometry_parser_set_callback(_navmesh_source_geometry_parser, _navmesh_source_geometry_parsing_callback);
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::navmesh_parse_source_geometry(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 Vector2 safe_scale = obstacle->get_global_scale().abs().maxf(0.001);
|
||||
const float obstacle_radius = obstacle->get_radius();
|
||||
|
||||
if (obstacle_radius > 0.0) {
|
||||
// Radius defined obstacle should be uniformly scaled from obstacle basis max scale axis.
|
||||
const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
|
||||
const Vector2 uniform_max_scale = Vector2(scaling_max_value, scaling_max_value);
|
||||
const Transform2D obstacle_circle_transform = p_source_geometry_data->root_node_transform * Transform2D(obstacle->get_global_rotation(), uniform_max_scale, 0.0, obstacle->get_global_position());
|
||||
|
||||
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] = obstacle_circle_transform.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());
|
||||
}
|
||||
|
||||
// Obstacles are projected to the xz-plane, so only rotation around the y-axis can be taken into account.
|
||||
const Transform2D node_xform = p_source_geometry_data->root_node_transform * obstacle->get_global_transform();
|
||||
|
||||
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 NavigationObstacle2D::_update_map(RID p_map) {
|
||||
map_current = p_map;
|
||||
NavigationServer2D::get_singleton()->obstacle_set_map(obstacle, p_map);
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::_update_position(const Vector2 p_position) {
|
||||
NavigationServer2D::get_singleton()->obstacle_set_position(obstacle, p_position);
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationObstacle2D::_update_transform() {
|
||||
_update_position(get_global_position());
|
||||
// Prevent non-positive or non-uniform scaling of dynamic obstacle radius.
|
||||
const Vector2 safe_scale = get_global_scale().abs().maxf(0.001);
|
||||
const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
|
||||
NavigationServer2D::get_singleton()->obstacle_set_radius(obstacle, scaling_max_value * radius);
|
||||
NavigationServer2D::get_singleton()->obstacle_set_vertices(obstacle, get_global_transform().translated(-get_global_position()).xform(vertices));
|
||||
#ifdef DEBUG_ENABLED
|
||||
queue_redraw();
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationObstacle2D::_update_fake_agent_radius_debug() {
|
||||
if (radius > 0.0 && NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_radius()) {
|
||||
Color debug_radius_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_obstacles_radius_color();
|
||||
// Prevent non-positive scaling.
|
||||
const Vector2 safe_scale = get_global_scale().abs().maxf(0.001);
|
||||
// Agent radius is a scalar value and does not support non-uniform scaling, choose the largest axis.
|
||||
const float scaling_max_value = safe_scale[safe_scale.max_axis_index()];
|
||||
RS::get_singleton()->canvas_item_add_circle(debug_canvas_item, get_global_position(), scaling_max_value * radius, debug_radius_color);
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationObstacle2D::_update_static_obstacle_debug() {
|
||||
if (get_vertices().size() < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_enable_obstacles_static()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
|
||||
rs->mesh_clear(debug_mesh_rid);
|
||||
|
||||
const int vertex_count = vertices.size();
|
||||
|
||||
Vector<Vector2> edge_vertex_array;
|
||||
edge_vertex_array.resize(vertex_count * 4);
|
||||
|
||||
Vector2 *edge_vertex_array_ptrw = edge_vertex_array.ptrw();
|
||||
|
||||
int vertex_index = 0;
|
||||
|
||||
for (int i = 0; i < vertex_count; i++) {
|
||||
Vector2 point = vertices[i];
|
||||
Vector2 next_point = vertices[(i + 1) % vertex_count];
|
||||
|
||||
Vector2 direction = next_point.direction_to(point);
|
||||
Vector2 arrow_dir = -direction.orthogonal();
|
||||
Vector2 edge_middle = point + ((next_point - point) * 0.5);
|
||||
|
||||
edge_vertex_array_ptrw[vertex_index++] = edge_middle;
|
||||
edge_vertex_array_ptrw[vertex_index++] = edge_middle + (arrow_dir * 10.0);
|
||||
|
||||
edge_vertex_array_ptrw[vertex_index++] = point;
|
||||
edge_vertex_array_ptrw[vertex_index++] = next_point;
|
||||
}
|
||||
|
||||
Color debug_static_obstacle_edge_color;
|
||||
|
||||
if (are_vertices_valid()) {
|
||||
debug_static_obstacle_edge_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushout_edge_color();
|
||||
} else {
|
||||
debug_static_obstacle_edge_color = NavigationServer2D::get_singleton()->get_debug_navigation_avoidance_static_obstacle_pushin_edge_color();
|
||||
}
|
||||
|
||||
Vector<Color> line_color_array;
|
||||
line_color_array.resize(edge_vertex_array.size());
|
||||
line_color_array.fill(debug_static_obstacle_edge_color);
|
||||
|
||||
Array edge_mesh_array;
|
||||
edge_mesh_array.resize(Mesh::ARRAY_MAX);
|
||||
edge_mesh_array[Mesh::ARRAY_VERTEX] = edge_vertex_array;
|
||||
edge_mesh_array[Mesh::ARRAY_COLOR] = line_color_array;
|
||||
|
||||
rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_LINES, edge_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
|
||||
|
||||
rs->canvas_item_add_mesh(debug_canvas_item, debug_mesh_rid, get_global_transform());
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
132
scene/2d/navigation/navigation_obstacle_2d.h
Normal file
132
scene/2d/navigation/navigation_obstacle_2d.h
Normal file
@@ -0,0 +1,132 @@
|
||||
/**************************************************************************/
|
||||
/* navigation_obstacle_2d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/2d/node_2d.h"
|
||||
|
||||
class NavigationPolygon;
|
||||
class NavigationMeshSourceGeometryData2D;
|
||||
|
||||
class NavigationObstacle2D : public Node2D {
|
||||
GDCLASS(NavigationObstacle2D, Node2D);
|
||||
|
||||
RID obstacle;
|
||||
RID map_before_pause;
|
||||
RID map_override;
|
||||
RID map_current;
|
||||
|
||||
real_t radius = 0.0;
|
||||
|
||||
Vector<Vector2> vertices;
|
||||
bool vertices_are_clockwise = true;
|
||||
bool vertices_are_valid = true;
|
||||
|
||||
bool avoidance_enabled = true;
|
||||
uint32_t avoidance_layers = 1;
|
||||
|
||||
Transform2D previous_transform;
|
||||
|
||||
Vector2 velocity;
|
||||
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;
|
||||
RID debug_mesh_rid;
|
||||
|
||||
void _update_fake_agent_radius_debug();
|
||||
void _update_static_obstacle_debug();
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
protected:
|
||||
static void _bind_methods();
|
||||
void _notification(int p_what);
|
||||
|
||||
public:
|
||||
NavigationObstacle2D();
|
||||
virtual ~NavigationObstacle2D();
|
||||
|
||||
RID get_rid() const { return obstacle; }
|
||||
|
||||
void set_avoidance_enabled(bool p_enabled);
|
||||
bool get_avoidance_enabled() const;
|
||||
|
||||
void set_navigation_map(RID p_navigation_map);
|
||||
RID get_navigation_map() const;
|
||||
|
||||
void set_radius(real_t p_radius);
|
||||
real_t get_radius() const { return radius; }
|
||||
|
||||
void set_vertices(const Vector<Vector2> &p_vertices);
|
||||
const Vector<Vector2> &get_vertices() const { return vertices; }
|
||||
|
||||
bool are_vertices_clockwise() const { return vertices_are_clockwise; }
|
||||
bool are_vertices_valid() const { return vertices_are_valid; }
|
||||
|
||||
void set_avoidance_layers(uint32_t p_layers);
|
||||
uint32_t get_avoidance_layers() const;
|
||||
|
||||
void set_avoidance_mask(uint32_t p_mask);
|
||||
uint32_t get_avoidance_mask() const;
|
||||
|
||||
void set_avoidance_layer_value(int p_layer_number, bool p_value);
|
||||
bool get_avoidance_layer_value(int p_layer_number) const;
|
||||
|
||||
void set_velocity(const Vector2 p_velocity);
|
||||
Vector2 get_velocity() const { return velocity; }
|
||||
|
||||
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;
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
private:
|
||||
static Callable _navmesh_source_geometry_parsing_callback;
|
||||
static RID _navmesh_source_geometry_parser;
|
||||
|
||||
public:
|
||||
static void navmesh_parse_init();
|
||||
static void navmesh_parse_source_geometry(const Ref<NavigationPolygon> &p_navigation_mesh, Ref<NavigationMeshSourceGeometryData2D> p_source_geometry_data, Node *p_node);
|
||||
|
||||
private:
|
||||
void _update_map(RID p_map);
|
||||
void _update_position(const Vector2 p_position);
|
||||
void _update_transform();
|
||||
};
|
676
scene/2d/navigation/navigation_region_2d.cpp
Normal file
676
scene/2d/navigation/navigation_region_2d.cpp
Normal file
@@ -0,0 +1,676 @@
|
||||
/**************************************************************************/
|
||||
/* navigation_region_2d.cpp */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#include "navigation_region_2d.h"
|
||||
|
||||
#include "core/math/random_pcg.h"
|
||||
#include "scene/resources/world_2d.h"
|
||||
#include "servers/navigation_server_2d.h"
|
||||
|
||||
RID NavigationRegion2D::get_rid() const {
|
||||
return region;
|
||||
}
|
||||
|
||||
void NavigationRegion2D::set_enabled(bool p_enabled) {
|
||||
if (enabled == p_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
enabled = p_enabled;
|
||||
|
||||
NavigationServer2D::get_singleton()->region_set_enabled(region, enabled);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (Engine::get_singleton()->is_editor_hint() || NavigationServer2D::get_singleton()->get_debug_navigation_enabled()) {
|
||||
queue_redraw();
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
bool NavigationRegion2D::is_enabled() const {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
void NavigationRegion2D::set_use_edge_connections(bool p_enabled) {
|
||||
if (use_edge_connections == p_enabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
use_edge_connections = p_enabled;
|
||||
|
||||
NavigationServer2D::get_singleton()->region_set_use_edge_connections(region, use_edge_connections);
|
||||
}
|
||||
|
||||
bool NavigationRegion2D::get_use_edge_connections() const {
|
||||
return use_edge_connections;
|
||||
}
|
||||
|
||||
void NavigationRegion2D::set_navigation_layers(uint32_t p_navigation_layers) {
|
||||
if (navigation_layers == p_navigation_layers) {
|
||||
return;
|
||||
}
|
||||
|
||||
navigation_layers = p_navigation_layers;
|
||||
|
||||
NavigationServer2D::get_singleton()->region_set_navigation_layers(region, navigation_layers);
|
||||
}
|
||||
|
||||
uint32_t NavigationRegion2D::get_navigation_layers() const {
|
||||
return navigation_layers;
|
||||
}
|
||||
|
||||
void NavigationRegion2D::set_navigation_layer_value(int p_layer_number, bool p_value) {
|
||||
ERR_FAIL_COND_MSG(p_layer_number < 1, "Navigation layer number must be between 1 and 32 inclusive.");
|
||||
ERR_FAIL_COND_MSG(p_layer_number > 32, "Navigation layer number must be between 1 and 32 inclusive.");
|
||||
|
||||
uint32_t _navigation_layers = get_navigation_layers();
|
||||
|
||||
if (p_value) {
|
||||
_navigation_layers |= 1 << (p_layer_number - 1);
|
||||
} else {
|
||||
_navigation_layers &= ~(1 << (p_layer_number - 1));
|
||||
}
|
||||
|
||||
set_navigation_layers(_navigation_layers);
|
||||
}
|
||||
|
||||
bool NavigationRegion2D::get_navigation_layer_value(int p_layer_number) const {
|
||||
ERR_FAIL_COND_V_MSG(p_layer_number < 1, false, "Navigation layer number must be between 1 and 32 inclusive.");
|
||||
ERR_FAIL_COND_V_MSG(p_layer_number > 32, false, "Navigation layer number must be between 1 and 32 inclusive.");
|
||||
|
||||
return get_navigation_layers() & (1 << (p_layer_number - 1));
|
||||
}
|
||||
|
||||
void NavigationRegion2D::set_enter_cost(real_t p_enter_cost) {
|
||||
ERR_FAIL_COND_MSG(p_enter_cost < 0.0, "The enter_cost must be positive.");
|
||||
if (Math::is_equal_approx(enter_cost, p_enter_cost)) {
|
||||
return;
|
||||
}
|
||||
|
||||
enter_cost = p_enter_cost;
|
||||
|
||||
NavigationServer2D::get_singleton()->region_set_enter_cost(region, enter_cost);
|
||||
}
|
||||
|
||||
real_t NavigationRegion2D::get_enter_cost() const {
|
||||
return enter_cost;
|
||||
}
|
||||
|
||||
void NavigationRegion2D::set_travel_cost(real_t p_travel_cost) {
|
||||
ERR_FAIL_COND_MSG(p_travel_cost < 0.0, "The travel_cost must be positive.");
|
||||
if (Math::is_equal_approx(travel_cost, p_travel_cost)) {
|
||||
return;
|
||||
}
|
||||
|
||||
travel_cost = p_travel_cost;
|
||||
|
||||
NavigationServer2D::get_singleton()->region_set_travel_cost(region, travel_cost);
|
||||
}
|
||||
|
||||
real_t NavigationRegion2D::get_travel_cost() const {
|
||||
return travel_cost;
|
||||
}
|
||||
|
||||
RID NavigationRegion2D::get_region_rid() const {
|
||||
return get_rid();
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
Rect2 NavigationRegion2D::_edit_get_rect() const {
|
||||
return navigation_polygon.is_valid() ? navigation_polygon->_edit_get_rect() : Rect2();
|
||||
}
|
||||
|
||||
bool NavigationRegion2D::_edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const {
|
||||
return navigation_polygon.is_valid() ? navigation_polygon->_edit_is_selected_on_click(p_point, p_tolerance) : false;
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
void NavigationRegion2D::_notification(int p_what) {
|
||||
switch (p_what) {
|
||||
case NOTIFICATION_ENTER_TREE: {
|
||||
_region_enter_navigation_map();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_TRANSFORM_CHANGED: {
|
||||
set_physics_process_internal(true);
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_VISIBILITY_CHANGED: {
|
||||
#ifdef DEBUG_ENABLED
|
||||
_set_debug_visible(is_visible_in_tree());
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_EXIT_TREE: {
|
||||
_region_exit_navigation_map();
|
||||
#ifdef DEBUG_ENABLED
|
||||
_set_debug_visible(false);
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_INTERNAL_PHYSICS_PROCESS: {
|
||||
set_physics_process_internal(false);
|
||||
_region_update_transform();
|
||||
} break;
|
||||
|
||||
case NOTIFICATION_DRAW: {
|
||||
#ifdef DEBUG_ENABLED
|
||||
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || (NavigationServer2D::get_singleton()->get_debug_enabled() && NavigationServer2D::get_singleton()->get_debug_navigation_enabled())) && navigation_polygon.is_valid()) {
|
||||
_update_debug_mesh();
|
||||
_update_debug_edge_connections_mesh();
|
||||
_update_debug_baking_rect();
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
} break;
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationRegion2D::set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon) {
|
||||
if (navigation_polygon.is_valid()) {
|
||||
navigation_polygon->disconnect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed));
|
||||
}
|
||||
|
||||
navigation_polygon = p_navigation_polygon;
|
||||
|
||||
if (navigation_polygon.is_valid()) {
|
||||
navigation_polygon->connect_changed(callable_mp(this, &NavigationRegion2D::_navigation_polygon_changed));
|
||||
}
|
||||
|
||||
_navigation_polygon_changed();
|
||||
}
|
||||
|
||||
Ref<NavigationPolygon> NavigationRegion2D::get_navigation_polygon() const {
|
||||
return navigation_polygon;
|
||||
}
|
||||
|
||||
void NavigationRegion2D::set_navigation_map(RID p_navigation_map) {
|
||||
if (map_override == p_navigation_map) {
|
||||
return;
|
||||
}
|
||||
|
||||
map_override = p_navigation_map;
|
||||
|
||||
NavigationServer2D::get_singleton()->region_set_map(region, map_override);
|
||||
}
|
||||
|
||||
RID NavigationRegion2D::get_navigation_map() const {
|
||||
if (map_override.is_valid()) {
|
||||
return map_override;
|
||||
} else if (is_inside_tree()) {
|
||||
return get_world_2d()->get_navigation_map();
|
||||
}
|
||||
return RID();
|
||||
}
|
||||
|
||||
void NavigationRegion2D::bake_navigation_polygon(bool p_on_thread) {
|
||||
ERR_FAIL_COND_MSG(!Thread::is_main_thread(), "The SceneTree can only be parsed on the main thread. Call this function from the main thread or use call_deferred().");
|
||||
ERR_FAIL_COND_MSG(navigation_polygon.is_null(), "Baking the navigation polygon requires a valid `NavigationPolygon` resource.");
|
||||
|
||||
Ref<NavigationMeshSourceGeometryData2D> source_geometry_data;
|
||||
source_geometry_data.instantiate();
|
||||
|
||||
NavigationServer2D::get_singleton()->parse_source_geometry_data(navigation_polygon, source_geometry_data, this);
|
||||
|
||||
if (p_on_thread) {
|
||||
NavigationServer2D::get_singleton()->bake_from_source_geometry_data_async(navigation_polygon, source_geometry_data, callable_mp(this, &NavigationRegion2D::_bake_finished));
|
||||
} else {
|
||||
NavigationServer2D::get_singleton()->bake_from_source_geometry_data(navigation_polygon, source_geometry_data, callable_mp(this, &NavigationRegion2D::_bake_finished));
|
||||
}
|
||||
}
|
||||
|
||||
void NavigationRegion2D::_bake_finished() {
|
||||
if (!Thread::is_main_thread()) {
|
||||
callable_mp(this, &NavigationRegion2D::_bake_finished).call_deferred();
|
||||
return;
|
||||
}
|
||||
|
||||
emit_signal(SNAME("bake_finished"));
|
||||
}
|
||||
|
||||
bool NavigationRegion2D::is_baking() const {
|
||||
return NavigationServer2D::get_singleton()->is_baking_navigation_polygon(navigation_polygon);
|
||||
}
|
||||
|
||||
void NavigationRegion2D::_navigation_polygon_changed() {
|
||||
_update_bounds();
|
||||
|
||||
NavigationServer2D::get_singleton()->region_set_navigation_polygon(region, navigation_polygon);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
debug_mesh_dirty = true;
|
||||
|
||||
if (navigation_polygon.is_null()) {
|
||||
_set_debug_visible(false);
|
||||
}
|
||||
|
||||
if (is_inside_tree() && (Engine::get_singleton()->is_editor_hint() || get_tree()->is_debugging_navigation_hint())) {
|
||||
queue_redraw();
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
emit_signal(SNAME("navigation_polygon_changed"));
|
||||
|
||||
update_configuration_warnings();
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationRegion2D::_navigation_map_changed(RID p_map) {
|
||||
if (is_inside_tree() && get_world_2d()->get_navigation_map() == p_map) {
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationRegion2D::_navigation_debug_changed() {
|
||||
if (is_inside_tree()) {
|
||||
queue_redraw();
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
PackedStringArray NavigationRegion2D::get_configuration_warnings() const {
|
||||
PackedStringArray warnings = Node2D::get_configuration_warnings();
|
||||
|
||||
if (is_visible_in_tree() && is_inside_tree()) {
|
||||
if (navigation_polygon.is_null()) {
|
||||
warnings.push_back(RTR("A NavigationPolygon resource must be set or created for this node to work. Please set a property or draw a polygon."));
|
||||
}
|
||||
}
|
||||
|
||||
return warnings;
|
||||
}
|
||||
|
||||
void NavigationRegion2D::_bind_methods() {
|
||||
ClassDB::bind_method(D_METHOD("get_rid"), &NavigationRegion2D::get_rid);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_navigation_polygon", "navigation_polygon"), &NavigationRegion2D::set_navigation_polygon);
|
||||
ClassDB::bind_method(D_METHOD("get_navigation_polygon"), &NavigationRegion2D::get_navigation_polygon);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &NavigationRegion2D::set_enabled);
|
||||
ClassDB::bind_method(D_METHOD("is_enabled"), &NavigationRegion2D::is_enabled);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_navigation_map", "navigation_map"), &NavigationRegion2D::set_navigation_map);
|
||||
ClassDB::bind_method(D_METHOD("get_navigation_map"), &NavigationRegion2D::get_navigation_map);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_use_edge_connections", "enabled"), &NavigationRegion2D::set_use_edge_connections);
|
||||
ClassDB::bind_method(D_METHOD("get_use_edge_connections"), &NavigationRegion2D::get_use_edge_connections);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_navigation_layers", "navigation_layers"), &NavigationRegion2D::set_navigation_layers);
|
||||
ClassDB::bind_method(D_METHOD("get_navigation_layers"), &NavigationRegion2D::get_navigation_layers);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_navigation_layer_value", "layer_number", "value"), &NavigationRegion2D::set_navigation_layer_value);
|
||||
ClassDB::bind_method(D_METHOD("get_navigation_layer_value", "layer_number"), &NavigationRegion2D::get_navigation_layer_value);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_region_rid"), &NavigationRegion2D::get_region_rid);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_enter_cost", "enter_cost"), &NavigationRegion2D::set_enter_cost);
|
||||
ClassDB::bind_method(D_METHOD("get_enter_cost"), &NavigationRegion2D::get_enter_cost);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("set_travel_cost", "travel_cost"), &NavigationRegion2D::set_travel_cost);
|
||||
ClassDB::bind_method(D_METHOD("get_travel_cost"), &NavigationRegion2D::get_travel_cost);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("bake_navigation_polygon", "on_thread"), &NavigationRegion2D::bake_navigation_polygon, DEFVAL(true));
|
||||
ClassDB::bind_method(D_METHOD("is_baking"), &NavigationRegion2D::is_baking);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("_navigation_polygon_changed"), &NavigationRegion2D::_navigation_polygon_changed);
|
||||
|
||||
ClassDB::bind_method(D_METHOD("get_bounds"), &NavigationRegion2D::get_bounds);
|
||||
|
||||
ADD_PROPERTY(PropertyInfo(Variant::OBJECT, "navigation_polygon", PROPERTY_HINT_RESOURCE_TYPE, "NavigationPolygon"), "set_navigation_polygon", "get_navigation_polygon");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "enabled"), "set_enabled", "is_enabled");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::BOOL, "use_edge_connections"), "set_use_edge_connections", "get_use_edge_connections");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::INT, "navigation_layers", PROPERTY_HINT_LAYERS_2D_NAVIGATION), "set_navigation_layers", "get_navigation_layers");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "enter_cost"), "set_enter_cost", "get_enter_cost");
|
||||
ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "travel_cost"), "set_travel_cost", "get_travel_cost");
|
||||
|
||||
ADD_SIGNAL(MethodInfo("navigation_polygon_changed"));
|
||||
ADD_SIGNAL(MethodInfo("bake_finished"));
|
||||
}
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
// Compatibility with earlier 4.0 betas.
|
||||
bool NavigationRegion2D::_set(const StringName &p_name, const Variant &p_value) {
|
||||
if (p_name == "navpoly") {
|
||||
set_navigation_polygon(p_value);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool NavigationRegion2D::_get(const StringName &p_name, Variant &r_ret) const {
|
||||
if (p_name == "navpoly") {
|
||||
r_ret = get_navigation_polygon();
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
NavigationRegion2D::NavigationRegion2D() {
|
||||
set_notify_transform(true);
|
||||
set_hide_clip_children(true);
|
||||
|
||||
region = NavigationServer2D::get_singleton()->region_create();
|
||||
NavigationServer2D::get_singleton()->region_set_owner_id(region, get_instance_id());
|
||||
NavigationServer2D::get_singleton()->region_set_enter_cost(region, get_enter_cost());
|
||||
NavigationServer2D::get_singleton()->region_set_travel_cost(region, get_travel_cost());
|
||||
NavigationServer2D::get_singleton()->region_set_navigation_layers(region, navigation_layers);
|
||||
NavigationServer2D::get_singleton()->region_set_use_edge_connections(region, use_edge_connections);
|
||||
NavigationServer2D::get_singleton()->region_set_enabled(region, enabled);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
NavigationServer2D::get_singleton()->connect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed));
|
||||
NavigationServer2D::get_singleton()->connect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_debug_changed));
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
NavigationRegion2D::~NavigationRegion2D() {
|
||||
ERR_FAIL_NULL(NavigationServer2D::get_singleton());
|
||||
NavigationServer2D::get_singleton()->free(region);
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
NavigationServer2D::get_singleton()->disconnect(SNAME("map_changed"), callable_mp(this, &NavigationRegion2D::_navigation_map_changed));
|
||||
NavigationServer2D::get_singleton()->disconnect(SNAME("navigation_debug_changed"), callable_mp(this, &NavigationRegion2D::_navigation_debug_changed));
|
||||
if (debug_instance_rid.is_valid()) {
|
||||
RS::get_singleton()->free(debug_instance_rid);
|
||||
}
|
||||
if (debug_mesh_rid.is_valid()) {
|
||||
RS::get_singleton()->free(debug_mesh_rid);
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
}
|
||||
|
||||
void NavigationRegion2D::_region_enter_navigation_map() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (map_override.is_valid()) {
|
||||
NavigationServer2D::get_singleton()->region_set_map(region, map_override);
|
||||
} else {
|
||||
NavigationServer2D::get_singleton()->region_set_map(region, get_world_2d()->get_navigation_map());
|
||||
}
|
||||
|
||||
current_global_transform = get_global_transform();
|
||||
NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform);
|
||||
|
||||
NavigationServer2D::get_singleton()->region_set_enabled(region, enabled);
|
||||
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
void NavigationRegion2D::_region_exit_navigation_map() {
|
||||
NavigationServer2D::get_singleton()->region_set_map(region, RID());
|
||||
}
|
||||
|
||||
void NavigationRegion2D::_region_update_transform() {
|
||||
if (!is_inside_tree()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Transform2D new_global_transform = get_global_transform();
|
||||
if (current_global_transform != new_global_transform) {
|
||||
current_global_transform = new_global_transform;
|
||||
NavigationServer2D::get_singleton()->region_set_transform(region, current_global_transform);
|
||||
}
|
||||
|
||||
queue_redraw();
|
||||
}
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationRegion2D::_update_debug_mesh() {
|
||||
if (!is_inside_tree()) {
|
||||
_set_debug_visible(false);
|
||||
return;
|
||||
}
|
||||
|
||||
const NavigationServer2D *ns2d = NavigationServer2D::get_singleton();
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
|
||||
if (!debug_instance_rid.is_valid()) {
|
||||
debug_instance_rid = rs->canvas_item_create();
|
||||
}
|
||||
if (!debug_mesh_rid.is_valid()) {
|
||||
debug_mesh_rid = rs->mesh_create();
|
||||
}
|
||||
|
||||
const Transform2D region_gt = get_global_transform();
|
||||
|
||||
rs->canvas_item_set_parent(debug_instance_rid, get_world_2d()->get_canvas());
|
||||
rs->canvas_item_set_z_index(debug_instance_rid, RS::CANVAS_ITEM_Z_MAX - 2);
|
||||
rs->canvas_item_set_transform(debug_instance_rid, region_gt);
|
||||
|
||||
if (!debug_mesh_dirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
rs->canvas_item_clear(debug_instance_rid);
|
||||
rs->mesh_clear(debug_mesh_rid);
|
||||
debug_mesh_dirty = false;
|
||||
|
||||
const Vector<Vector2> &vertices = navigation_polygon->get_vertices();
|
||||
if (vertices.size() < 3) {
|
||||
return;
|
||||
}
|
||||
|
||||
int polygon_count = navigation_polygon->get_polygon_count();
|
||||
if (polygon_count == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool enabled_geometry_face_random_color = ns2d->get_debug_navigation_enable_geometry_face_random_color();
|
||||
bool enabled_edge_lines = ns2d->get_debug_navigation_enable_edge_lines();
|
||||
|
||||
Color debug_face_color = ns2d->get_debug_navigation_geometry_face_color();
|
||||
Color debug_edge_color = ns2d->get_debug_navigation_geometry_edge_color();
|
||||
|
||||
if (!enabled) {
|
||||
debug_face_color = ns2d->get_debug_navigation_geometry_face_disabled_color();
|
||||
debug_edge_color = ns2d->get_debug_navigation_geometry_edge_disabled_color();
|
||||
}
|
||||
|
||||
int vertex_count = 0;
|
||||
int line_count = 0;
|
||||
|
||||
for (int i = 0; i < polygon_count; i++) {
|
||||
const Vector<int> &polygon = navigation_polygon->get_polygon(i);
|
||||
int polygon_size = polygon.size();
|
||||
if (polygon_size < 3) {
|
||||
continue;
|
||||
}
|
||||
line_count += polygon_size * 2;
|
||||
vertex_count += (polygon_size - 2) * 3;
|
||||
}
|
||||
|
||||
Vector<Vector2> face_vertex_array;
|
||||
face_vertex_array.resize(vertex_count);
|
||||
|
||||
Vector<Color> face_color_array;
|
||||
if (enabled_geometry_face_random_color) {
|
||||
face_color_array.resize(vertex_count);
|
||||
}
|
||||
|
||||
Vector<Vector2> line_vertex_array;
|
||||
if (enabled_edge_lines) {
|
||||
line_vertex_array.resize(line_count);
|
||||
}
|
||||
|
||||
RandomPCG rand;
|
||||
Color polygon_color = debug_face_color;
|
||||
|
||||
int face_vertex_index = 0;
|
||||
int line_vertex_index = 0;
|
||||
|
||||
Vector2 *face_vertex_array_ptrw = face_vertex_array.ptrw();
|
||||
Color *face_color_array_ptrw = face_color_array.ptrw();
|
||||
Vector2 *line_vertex_array_ptrw = line_vertex_array.ptrw();
|
||||
|
||||
for (int polygon_index = 0; polygon_index < polygon_count; polygon_index++) {
|
||||
const Vector<int> &polygon_indices = navigation_polygon->get_polygon(polygon_index);
|
||||
int polygon_indices_size = polygon_indices.size();
|
||||
if (polygon_indices_size < 3) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (enabled_geometry_face_random_color) {
|
||||
// Generate the polygon color, slightly randomly modified from the settings one.
|
||||
polygon_color.set_hsv(debug_face_color.get_h() + rand.random(-1.0, 1.0) * 0.1, debug_face_color.get_s(), debug_face_color.get_v() + rand.random(-1.0, 1.0) * 0.2);
|
||||
polygon_color.a = debug_face_color.a;
|
||||
}
|
||||
|
||||
for (int polygon_indices_index = 0; polygon_indices_index < polygon_indices_size - 2; polygon_indices_index++) {
|
||||
face_vertex_array_ptrw[face_vertex_index] = vertices[polygon_indices[0]];
|
||||
face_vertex_array_ptrw[face_vertex_index + 1] = vertices[polygon_indices[polygon_indices_index + 1]];
|
||||
face_vertex_array_ptrw[face_vertex_index + 2] = vertices[polygon_indices[polygon_indices_index + 2]];
|
||||
if (enabled_geometry_face_random_color) {
|
||||
face_color_array_ptrw[face_vertex_index] = polygon_color;
|
||||
face_color_array_ptrw[face_vertex_index + 1] = polygon_color;
|
||||
face_color_array_ptrw[face_vertex_index + 2] = polygon_color;
|
||||
}
|
||||
face_vertex_index += 3;
|
||||
}
|
||||
|
||||
if (enabled_edge_lines) {
|
||||
for (int polygon_indices_index = 0; polygon_indices_index < polygon_indices_size; polygon_indices_index++) {
|
||||
line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[polygon_indices_index]];
|
||||
line_vertex_index += 1;
|
||||
if (polygon_indices_index + 1 == polygon_indices_size) {
|
||||
line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[0]];
|
||||
line_vertex_index += 1;
|
||||
} else {
|
||||
line_vertex_array_ptrw[line_vertex_index] = vertices[polygon_indices[polygon_indices_index + 1]];
|
||||
line_vertex_index += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!enabled_geometry_face_random_color) {
|
||||
face_color_array.resize(face_vertex_array.size());
|
||||
face_color_array.fill(debug_face_color);
|
||||
}
|
||||
|
||||
Array face_mesh_array;
|
||||
face_mesh_array.resize(Mesh::ARRAY_MAX);
|
||||
face_mesh_array[Mesh::ARRAY_VERTEX] = face_vertex_array;
|
||||
face_mesh_array[Mesh::ARRAY_COLOR] = face_color_array;
|
||||
|
||||
rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_TRIANGLES, face_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
|
||||
|
||||
if (enabled_edge_lines) {
|
||||
Vector<Color> line_color_array;
|
||||
line_color_array.resize(line_vertex_array.size());
|
||||
line_color_array.fill(debug_edge_color);
|
||||
|
||||
Array line_mesh_array;
|
||||
line_mesh_array.resize(Mesh::ARRAY_MAX);
|
||||
line_mesh_array[Mesh::ARRAY_VERTEX] = line_vertex_array;
|
||||
line_mesh_array[Mesh::ARRAY_COLOR] = line_color_array;
|
||||
|
||||
rs->mesh_add_surface_from_arrays(debug_mesh_rid, RS::PRIMITIVE_LINES, line_mesh_array, Array(), Dictionary(), RS::ARRAY_FLAG_USE_2D_VERTICES);
|
||||
}
|
||||
|
||||
rs->canvas_item_add_mesh(debug_instance_rid, debug_mesh_rid, Transform2D());
|
||||
rs->canvas_item_set_visible(debug_instance_rid, is_visible_in_tree());
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationRegion2D::_update_debug_edge_connections_mesh() {
|
||||
const NavigationServer2D *ns2d = NavigationServer2D::get_singleton();
|
||||
bool enable_edge_connections = use_edge_connections && ns2d->get_debug_navigation_enable_edge_connections() && ns2d->map_get_use_edge_connections(get_world_2d()->get_navigation_map());
|
||||
|
||||
if (enable_edge_connections) {
|
||||
Color debug_edge_connection_color = ns2d->get_debug_navigation_edge_connection_color();
|
||||
// Draw the region edge connections.
|
||||
Transform2D xform = get_global_transform();
|
||||
real_t radius = ns2d->map_get_edge_connection_margin(get_world_2d()->get_navigation_map()) / 2.0;
|
||||
for (int i = 0; i < ns2d->region_get_connections_count(region); i++) {
|
||||
// Two main points
|
||||
Vector2 a = ns2d->region_get_connection_pathway_start(region, i);
|
||||
a = xform.affine_inverse().xform(a);
|
||||
Vector2 b = ns2d->region_get_connection_pathway_end(region, i);
|
||||
b = xform.affine_inverse().xform(b);
|
||||
draw_line(a, b, debug_edge_connection_color);
|
||||
|
||||
// Draw a circle to illustrate the margins.
|
||||
real_t angle = a.angle_to_point(b);
|
||||
draw_arc(a, radius, angle + Math::PI / 2.0, angle - Math::PI / 2.0 + Math::TAU, 10, debug_edge_connection_color);
|
||||
draw_arc(b, radius, angle - Math::PI / 2.0, angle + Math::PI / 2.0, 10, debug_edge_connection_color);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationRegion2D::_update_debug_baking_rect() {
|
||||
Rect2 baking_rect = get_navigation_polygon()->get_baking_rect();
|
||||
if (baking_rect.has_area()) {
|
||||
Vector2 baking_rect_offset = get_navigation_polygon()->get_baking_rect_offset();
|
||||
Rect2 debug_baking_rect = Rect2(baking_rect.position.x + baking_rect_offset.x, baking_rect.position.y + baking_rect_offset.y, baking_rect.size.x, baking_rect.size.y);
|
||||
Color debug_baking_rect_color = Color(0.8, 0.5, 0.7, 0.1);
|
||||
draw_rect(debug_baking_rect, debug_baking_rect_color);
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
void NavigationRegion2D::_set_debug_visible(bool p_visible) {
|
||||
RenderingServer *rs = RenderingServer::get_singleton();
|
||||
ERR_FAIL_NULL(rs);
|
||||
if (debug_instance_rid.is_valid()) {
|
||||
RS::get_singleton()->canvas_item_set_visible(debug_instance_rid, p_visible);
|
||||
}
|
||||
}
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
void NavigationRegion2D::_update_bounds() {
|
||||
if (navigation_polygon.is_null()) {
|
||||
bounds = Rect2();
|
||||
return;
|
||||
}
|
||||
|
||||
const Vector<Vector2> &vertices = navigation_polygon->get_vertices();
|
||||
if (vertices.is_empty()) {
|
||||
bounds = Rect2();
|
||||
return;
|
||||
}
|
||||
|
||||
const Transform2D gt = is_inside_tree() ? get_global_transform() : get_transform();
|
||||
|
||||
Rect2 new_bounds;
|
||||
new_bounds.position = gt.xform(vertices[0]);
|
||||
|
||||
for (const Vector2 &vertex : vertices) {
|
||||
new_bounds.expand_to(gt.xform(vertex));
|
||||
}
|
||||
bounds = new_bounds;
|
||||
}
|
127
scene/2d/navigation/navigation_region_2d.h
Normal file
127
scene/2d/navigation/navigation_region_2d.h
Normal file
@@ -0,0 +1,127 @@
|
||||
/**************************************************************************/
|
||||
/* navigation_region_2d.h */
|
||||
/**************************************************************************/
|
||||
/* This file is part of: */
|
||||
/* GODOT ENGINE */
|
||||
/* https://godotengine.org */
|
||||
/**************************************************************************/
|
||||
/* Copyright (c) 2014-present Godot Engine contributors (see AUTHORS.md). */
|
||||
/* Copyright (c) 2007-2014 Juan Linietsky, Ariel Manzur. */
|
||||
/* */
|
||||
/* Permission is hereby granted, free of charge, to any person obtaining */
|
||||
/* a copy of this software and associated documentation files (the */
|
||||
/* "Software"), to deal in the Software without restriction, including */
|
||||
/* without limitation the rights to use, copy, modify, merge, publish, */
|
||||
/* distribute, sublicense, and/or sell copies of the Software, and to */
|
||||
/* permit persons to whom the Software is furnished to do so, subject to */
|
||||
/* the following conditions: */
|
||||
/* */
|
||||
/* The above copyright notice and this permission notice shall be */
|
||||
/* included in all copies or substantial portions of the Software. */
|
||||
/* */
|
||||
/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */
|
||||
/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */
|
||||
/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. */
|
||||
/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */
|
||||
/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */
|
||||
/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */
|
||||
/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */
|
||||
/**************************************************************************/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "scene/resources/2d/navigation_polygon.h"
|
||||
|
||||
class NavigationRegion2D : public Node2D {
|
||||
GDCLASS(NavigationRegion2D, Node2D);
|
||||
|
||||
bool enabled = true;
|
||||
bool use_edge_connections = true;
|
||||
|
||||
RID region;
|
||||
RID map_override;
|
||||
uint32_t navigation_layers = 1;
|
||||
real_t enter_cost = 0.0;
|
||||
real_t travel_cost = 1.0;
|
||||
Ref<NavigationPolygon> navigation_polygon;
|
||||
|
||||
Transform2D current_global_transform;
|
||||
|
||||
void _navigation_polygon_changed();
|
||||
|
||||
Rect2 bounds;
|
||||
|
||||
#ifdef DEBUG_ENABLED
|
||||
private:
|
||||
RID debug_mesh_rid;
|
||||
RID debug_instance_rid;
|
||||
|
||||
bool debug_mesh_dirty = true;
|
||||
|
||||
void _set_debug_visible(bool p_visible);
|
||||
void _update_debug_mesh();
|
||||
void _update_debug_edge_connections_mesh();
|
||||
void _update_debug_baking_rect();
|
||||
void _navigation_map_changed(RID p_map);
|
||||
void _navigation_debug_changed();
|
||||
#endif // DEBUG_ENABLED
|
||||
|
||||
protected:
|
||||
void _notification(int p_what);
|
||||
static void _bind_methods();
|
||||
|
||||
#ifndef DISABLE_DEPRECATED
|
||||
bool _set(const StringName &p_name, const Variant &p_value);
|
||||
bool _get(const StringName &p_name, Variant &r_ret) const;
|
||||
#endif // DISABLE_DEPRECATED
|
||||
|
||||
public:
|
||||
#ifdef DEBUG_ENABLED
|
||||
virtual Rect2 _edit_get_rect() const override;
|
||||
virtual bool _edit_is_selected_on_click(const Point2 &p_point, double p_tolerance) const override;
|
||||
#endif // DEBUG_ENABLED
|
||||
RID get_rid() const;
|
||||
|
||||
void set_enabled(bool p_enabled);
|
||||
bool is_enabled() const;
|
||||
|
||||
void set_navigation_map(RID p_navigation_map);
|
||||
RID get_navigation_map() const;
|
||||
|
||||
void set_use_edge_connections(bool p_enabled);
|
||||
bool get_use_edge_connections() const;
|
||||
|
||||
void set_navigation_layers(uint32_t p_navigation_layers);
|
||||
uint32_t get_navigation_layers() const;
|
||||
|
||||
void set_navigation_layer_value(int p_layer_number, bool p_value);
|
||||
bool get_navigation_layer_value(int p_layer_number) const;
|
||||
|
||||
RID get_region_rid() const;
|
||||
|
||||
void set_enter_cost(real_t p_enter_cost);
|
||||
real_t get_enter_cost() const;
|
||||
|
||||
void set_travel_cost(real_t p_travel_cost);
|
||||
real_t get_travel_cost() const;
|
||||
|
||||
void set_navigation_polygon(const Ref<NavigationPolygon> &p_navigation_polygon);
|
||||
Ref<NavigationPolygon> get_navigation_polygon() const;
|
||||
|
||||
PackedStringArray get_configuration_warnings() const override;
|
||||
|
||||
void bake_navigation_polygon(bool p_on_thread);
|
||||
void _bake_finished();
|
||||
bool is_baking() const;
|
||||
|
||||
Rect2 get_bounds() const { return bounds; }
|
||||
|
||||
NavigationRegion2D();
|
||||
~NavigationRegion2D();
|
||||
|
||||
private:
|
||||
void _update_bounds();
|
||||
void _region_enter_navigation_map();
|
||||
void _region_exit_navigation_map();
|
||||
void _region_update_transform();
|
||||
};
|
Reference in New Issue
Block a user