diff options
Diffstat (limited to 'core')
| -rw-r--r-- | core/config/project_settings.cpp | 28 | ||||
| -rw-r--r-- | core/extension/native_extension.cpp | 4 | ||||
| -rw-r--r-- | core/io/dir_access.cpp | 14 | ||||
| -rw-r--r-- | core/io/dir_access.h | 2 | ||||
| -rw-r--r-- | core/io/file_access_pack.cpp | 2 | ||||
| -rw-r--r-- | core/io/resource_format_binary.cpp | 6 | ||||
| -rw-r--r-- | core/io/resource_importer.cpp | 2 | ||||
| -rw-r--r-- | core/io/resource_uid.cpp | 2 | ||||
| -rw-r--r-- | core/math/a_star_grid_2d.cpp | 589 | ||||
| -rw-r--r-- | core/math/a_star_grid_2d.h | 178 | ||||
| -rw-r--r-- | core/math/transform_3d.cpp | 13 | ||||
| -rw-r--r-- | core/math/transform_3d.h | 1 | ||||
| -rw-r--r-- | core/register_core_types.cpp | 2 | ||||
| -rw-r--r-- | core/string/ustring.cpp | 87 | ||||
| -rw-r--r-- | core/string/ustring.h | 7 | ||||
| -rw-r--r-- | core/variant/variant_call.cpp | 6 | 
16 files changed, 857 insertions, 86 deletions
diff --git a/core/config/project_settings.cpp b/core/config/project_settings.cpp index d46e242610..74c06123e1 100644 --- a/core/config/project_settings.cpp +++ b/core/config/project_settings.cpp @@ -72,7 +72,7 @@ String ProjectSettings::get_safe_project_name() const {  }  String ProjectSettings::get_imported_files_path() const { -	return get_project_data_path().plus_file("imported"); +	return get_project_data_path().path_join("imported");  }  // Returns the features that a project must have when opened with this build of Godot. @@ -157,12 +157,12 @@ String ProjectSettings::localize_path(const String &p_path) const {  		// in an absolute path that just happens to contain this string but points to a  		// different folder (e.g. "/my/project" as resource_path would be contained in  		// "/my/project_data", even though the latter is not part of res://. -		// `plus_file("")` is an easy way to ensure we have a trailing '/'. -		const String res_path = resource_path.plus_file(""); +		// `path_join("")` is an easy way to ensure we have a trailing '/'. +		const String res_path = resource_path.path_join("");  		// DirAccess::get_current_dir() is not guaranteed to return a path that with a trailing '/',  		// so we must make sure we have it as well in order to compare with 'res_path'. -		cwd = cwd.plus_file(""); +		cwd = cwd.path_join("");  		if (!cwd.begins_with(res_path)) {  			return p_path; @@ -472,7 +472,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b  		if (err == OK && !p_ignore_override) {  			// Load override from location of the main pack  			// Optional, we don't mind if it fails -			_load_settings_text(p_main_pack.get_base_dir().plus_file("override.cfg")); +			_load_settings_text(p_main_pack.get_base_dir().path_join("override.cfg"));  		}  		return err;  	} @@ -500,14 +500,14 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b  #ifdef MACOS_ENABLED  		if (!found) {  			// Attempt to load PCK from macOS .app bundle resources. -			found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().plus_file(exec_basename + ".pck")) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().plus_file(exec_filename + ".pck")); +			found = _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_basename + ".pck")) || _load_resource_pack(OS::get_singleton()->get_bundle_resource_dir().path_join(exec_filename + ".pck"));  		}  #endif  		if (!found) {  			// Try to load data pack at the location of the executable.  			// As mentioned above, we have two potential names to attempt. -			found = _load_resource_pack(exec_dir.plus_file(exec_basename + ".pck")) || _load_resource_pack(exec_dir.plus_file(exec_filename + ".pck")); +			found = _load_resource_pack(exec_dir.path_join(exec_basename + ".pck")) || _load_resource_pack(exec_dir.path_join(exec_filename + ".pck"));  		}  		if (!found) { @@ -523,7 +523,7 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b  				// Load overrides from the PCK and the executable location.  				// Optional, we don't mind if either fails.  				_load_settings_text("res://override.cfg"); -				_load_settings_text(exec_path.get_base_dir().plus_file("override.cfg")); +				_load_settings_text(exec_path.get_base_dir().path_join("override.cfg"));  			}  			return err;  		} @@ -556,10 +556,10 @@ Error ProjectSettings::_setup(const String &p_path, const String &p_main_pack, b  		// Set the resource path early so things can be resolved when loading.  		resource_path = current_dir;  		resource_path = resource_path.replace("\\", "/"); // Windows path to Unix path just in case. -		err = _load_settings_text_or_binary(current_dir.plus_file("project.godot"), current_dir.plus_file("project.binary")); +		err = _load_settings_text_or_binary(current_dir.path_join("project.godot"), current_dir.path_join("project.binary"));  		if (err == OK && !p_ignore_override) {  			// Optional, we don't mind if it fails. -			_load_settings_text(current_dir.plus_file("override.cfg")); +			_load_settings_text(current_dir.path_join("override.cfg"));  			found = true;  			break;  		} @@ -685,7 +685,7 @@ Error ProjectSettings::_load_settings_text(const String &p_path) {  			// If we're loading a project.godot from source code, we can operate some  			// ProjectSettings conversions if need be.  			_convert_to_last_version(config_version); -			last_save_time = FileAccess::get_modified_time(get_resource_path().plus_file("project.godot")); +			last_save_time = FileAccess::get_modified_time(get_resource_path().path_join("project.godot"));  			return OK;  		}  		ERR_FAIL_COND_V_MSG(err != OK, err, "Error parsing " + p_path + " at line " + itos(lines) + ": " + error_text + " File might be corrupted."); @@ -764,9 +764,9 @@ void ProjectSettings::clear(const String &p_name) {  }  Error ProjectSettings::save() { -	Error error = save_custom(get_resource_path().plus_file("project.godot")); +	Error error = save_custom(get_resource_path().path_join("project.godot"));  	if (error == OK) { -		last_save_time = FileAccess::get_modified_time(get_resource_path().plus_file("project.godot")); +		last_save_time = FileAccess::get_modified_time(get_resource_path().path_join("project.godot"));  	}  	return error;  } @@ -911,7 +911,7 @@ Error ProjectSettings::save_custom(const String &p_path, const CustomMap &p_cust  		}  	}  	// Check for the existence of a csproj file. -	if (FileAccess::exists(get_resource_path().plus_file(get_safe_project_name() + ".csproj"))) { +	if (FileAccess::exists(get_resource_path().path_join(get_safe_project_name() + ".csproj"))) {  		// If there is a csproj file, add the C# feature if it doesn't already exist.  		if (!project_features.has("C#")) {  			project_features.append("C#"); diff --git a/core/extension/native_extension.cpp b/core/extension/native_extension.cpp index fdb4e50d90..6418da2235 100644 --- a/core/extension/native_extension.cpp +++ b/core/extension/native_extension.cpp @@ -36,7 +36,7 @@  #include "core/os/os.h"  String NativeExtension::get_extension_list_config_file() { -	return ProjectSettings::get_singleton()->get_project_data_path().plus_file("extension_list.cfg"); +	return ProjectSettings::get_singleton()->get_project_data_path().path_join("extension_list.cfg");  }  class NativeExtensionMethodBind : public MethodBind { @@ -421,7 +421,7 @@ Ref<Resource> NativeExtensionResourceLoader::load(const String &p_path, const St  	}  	if (!library_path.is_resource_file() && !library_path.is_absolute_path()) { -		library_path = p_path.get_base_dir().plus_file(library_path); +		library_path = p_path.get_base_dir().path_join(library_path);  	}  	Ref<NativeExtension> lib; diff --git a/core/io/dir_access.cpp b/core/io/dir_access.cpp index f82d6f077f..bed41b8d89 100644 --- a/core/io/dir_access.cpp +++ b/core/io/dir_access.cpp @@ -106,7 +106,7 @@ static Error _erase_recursive(DirAccess *da) {  			if (err) {  				return err;  			} -			err = da->remove(da->get_current_dir().plus_file(E)); +			err = da->remove(da->get_current_dir().path_join(E));  			if (err) {  				return err;  			} @@ -116,7 +116,7 @@ static Error _erase_recursive(DirAccess *da) {  	}  	for (const String &E : files) { -		Error err = da->remove(da->get_current_dir().plus_file(E)); +		Error err = da->remove(da->get_current_dir().path_join(E));  		if (err) {  			return err;  		} @@ -138,7 +138,7 @@ Error DirAccess::make_dir_recursive(String p_dir) {  	if (p_dir.is_relative_path()) {  		//append current -		full_dir = get_current_dir().plus_file(p_dir); +		full_dir = get_current_dir().path_join(p_dir);  	} else {  		full_dir = p_dir; @@ -172,7 +172,7 @@ Error DirAccess::make_dir_recursive(String p_dir) {  	String curpath = base;  	for (int i = 0; i < subdirs.size(); i++) { -		curpath = curpath.plus_file(subdirs[i]); +		curpath = curpath.path_join(subdirs[i]);  		Error err = make_dir(curpath);  		if (err != OK && err != ERR_ALREADY_EXISTS) {  			ERR_FAIL_V_MSG(err, "Could not create directory: " + curpath); @@ -354,8 +354,8 @@ Error DirAccess::_copy_dir(Ref<DirAccess> &p_target_da, String p_to, int p_chmod  	String n = get_next();  	while (!n.is_empty()) {  		if (n != "." && n != "..") { -			if (p_copy_links && is_link(get_current_dir().plus_file(n))) { -				create_link(read_link(get_current_dir().plus_file(n)), p_to + n); +			if (p_copy_links && is_link(get_current_dir().path_join(n))) { +				create_link(read_link(get_current_dir().path_join(n)), p_to + n);  			} else if (current_is_dir()) {  				dirs.push_back(n);  			} else { @@ -364,7 +364,7 @@ Error DirAccess::_copy_dir(Ref<DirAccess> &p_target_da, String p_to, int p_chmod  					list_dir_end();  					return ERR_BUG;  				} -				Error err = copy(get_current_dir().plus_file(n), p_to + rel_path, p_chmod_flags); +				Error err = copy(get_current_dir().path_join(n), p_to + rel_path, p_chmod_flags);  				if (err) {  					list_dir_end();  					return err; diff --git a/core/io/dir_access.h b/core/io/dir_access.h index d5318dfb45..2469c2a080 100644 --- a/core/io/dir_access.h +++ b/core/io/dir_access.h @@ -55,7 +55,7 @@ private:  protected:  	String _get_root_path() const; -	String _get_root_string() const; +	virtual String _get_root_string() const;  	AccessType get_access_type() const;  	String fix_path(String p_path) const; diff --git a/core/io/file_access_pack.cpp b/core/io/file_access_pack.cpp index 595a6e9873..adae0db0f4 100644 --- a/core/io/file_access_pack.cpp +++ b/core/io/file_access_pack.cpp @@ -520,7 +520,7 @@ String DirAccessPack::get_current_dir(bool p_include_drive) const {  	while (pd->parent) {  		pd = pd->parent; -		p = pd->name.plus_file(p); +		p = pd->name.path_join(p);  	}  	return "res://" + p; diff --git a/core/io/resource_format_binary.cpp b/core/io/resource_format_binary.cpp index b731608b4f..4f1204fc48 100644 --- a/core/io/resource_format_binary.cpp +++ b/core/io/resource_format_binary.cpp @@ -421,7 +421,7 @@ Error ResourceLoaderBinary::parse_variant(Variant &r_v) {  					if (!path.contains("://") && path.is_relative_path()) {  						// path is relative to file being loaded, so convert to a resource path -						path = ProjectSettings::get_singleton()->localize_path(res_path.get_base_dir().plus_file(path)); +						path = ProjectSettings::get_singleton()->localize_path(res_path.get_base_dir().path_join(path));  					}  					if (remaps.find(path)) { @@ -683,7 +683,7 @@ Error ResourceLoaderBinary::load() {  		if (!path.contains("://") && path.is_relative_path()) {  			// path is relative to file being loaded, so convert to a resource path -			path = ProjectSettings::get_singleton()->localize_path(path.get_base_dir().plus_file(external_resources[i].path)); +			path = ProjectSettings::get_singleton()->localize_path(path.get_base_dir().path_join(external_resources[i].path));  		}  		external_resources.write[i].path = path; //remap happens here, not on load because on load it can actually be used for filesystem dock resource remap @@ -1329,7 +1329,7 @@ Error ResourceFormatLoaderBinary::rename_dependencies(const String &p_path, cons  		bool relative = false;  		if (!path.begins_with("res://")) { -			path = local_path.plus_file(path).simplify_path(); +			path = local_path.path_join(path).simplify_path();  			relative = true;  		} diff --git a/core/io/resource_importer.cpp b/core/io/resource_importer.cpp index e059fc842b..aa7f96a047 100644 --- a/core/io/resource_importer.cpp +++ b/core/io/resource_importer.cpp @@ -421,7 +421,7 @@ Ref<ResourceImporter> ResourceFormatImporter::get_importer_by_extension(const St  }  String ResourceFormatImporter::get_import_base_path(const String &p_for_file) const { -	return ProjectSettings::get_singleton()->get_imported_files_path().plus_file(p_for_file.get_file() + "-" + p_for_file.md5_text()); +	return ProjectSettings::get_singleton()->get_imported_files_path().path_join(p_for_file.get_file() + "-" + p_for_file.md5_text());  }  bool ResourceFormatImporter::are_import_settings_valid(const String &p_path) const { diff --git a/core/io/resource_uid.cpp b/core/io/resource_uid.cpp index fc324a26da..5324c5dd84 100644 --- a/core/io/resource_uid.cpp +++ b/core/io/resource_uid.cpp @@ -39,7 +39,7 @@ static constexpr uint32_t char_count = ('z' - 'a');  static constexpr uint32_t base = char_count + ('9' - '0');  String ResourceUID::get_cache_file() { -	return ProjectSettings::get_singleton()->get_project_data_path().plus_file("uid_cache.bin"); +	return ProjectSettings::get_singleton()->get_project_data_path().path_join("uid_cache.bin");  }  String ResourceUID::id_to_text(ID p_id) const { diff --git a/core/math/a_star_grid_2d.cpp b/core/math/a_star_grid_2d.cpp new file mode 100644 index 0000000000..8db33bde6f --- /dev/null +++ b/core/math/a_star_grid_2d.cpp @@ -0,0 +1,589 @@ +/*************************************************************************/ +/*  a_star_grid_2d.cpp                                                   */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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 "a_star_grid_2d.h" + +static real_t heuristic_manhattan(const Vector2i &p_from, const Vector2i &p_to) { +	real_t dx = (real_t)ABS(p_to.x - p_from.x); +	real_t dy = (real_t)ABS(p_to.y - p_from.y); +	return dx + dy; +} + +static real_t heuristic_euclidian(const Vector2i &p_from, const Vector2i &p_to) { +	real_t dx = (real_t)ABS(p_to.x - p_from.x); +	real_t dy = (real_t)ABS(p_to.y - p_from.y); +	return (real_t)Math::sqrt(dx * dx + dy * dy); +} + +static real_t heuristic_octile(const Vector2i &p_from, const Vector2i &p_to) { +	real_t dx = (real_t)ABS(p_to.x - p_from.x); +	real_t dy = (real_t)ABS(p_to.y - p_from.y); +	real_t F = Math_SQRT2 - 1; +	return (dx < dy) ? F * dx + dy : F * dy + dx; +} + +static real_t heuristic_chebyshev(const Vector2i &p_from, const Vector2i &p_to) { +	real_t dx = (real_t)ABS(p_to.x - p_from.x); +	real_t dy = (real_t)ABS(p_to.y - p_from.y); +	return MAX(dx, dy); +} + +static real_t (*heuristics[AStarGrid2D::HEURISTIC_MAX])(const Vector2i &, const Vector2i &) = { heuristic_manhattan, heuristic_euclidian, heuristic_octile, heuristic_chebyshev }; + +void AStarGrid2D::set_size(const Vector2i &p_size) { +	ERR_FAIL_COND(p_size.x < 0 || p_size.y < 0); +	if (p_size != size) { +		size = p_size; +		dirty = true; +	} +} + +Vector2i AStarGrid2D::get_size() const { +	return size; +} + +void AStarGrid2D::set_offset(const Vector2 &p_offset) { +	if (!offset.is_equal_approx(p_offset)) { +		offset = p_offset; +		dirty = true; +	} +} + +Vector2 AStarGrid2D::get_offset() const { +	return offset; +} + +void AStarGrid2D::set_cell_size(const Vector2 &p_cell_size) { +	if (!cell_size.is_equal_approx(p_cell_size)) { +		cell_size = p_cell_size; +		dirty = true; +	} +} + +Vector2 AStarGrid2D::get_cell_size() const { +	return cell_size; +} + +void AStarGrid2D::update() { +	points.clear(); +	for (int64_t y = 0; y < size.y; y++) { +		LocalVector<Point> line; +		for (int64_t x = 0; x < size.x; x++) { +			line.push_back(Point(Vector2i(x, y), offset + Vector2(x, y) * cell_size)); +		} +		points.push_back(line); +	} +	dirty = false; +} + +bool AStarGrid2D::is_in_bounds(int p_x, int p_y) const { +	return p_x >= 0 && p_x < size.width && p_y >= 0 && p_y < size.height; +} + +bool AStarGrid2D::is_in_boundsv(const Vector2i &p_id) const { +	return p_id.x >= 0 && p_id.x < size.width && p_id.y >= 0 && p_id.y < size.height; +} + +bool AStarGrid2D::is_dirty() const { +	return dirty; +} + +void AStarGrid2D::set_jumping_enabled(bool p_enabled) { +	jumping_enabled = p_enabled; +} + +bool AStarGrid2D::is_jumping_enabled() const { +	return jumping_enabled; +} + +void AStarGrid2D::set_diagonal_mode(DiagonalMode p_diagonal_mode) { +	ERR_FAIL_INDEX((int)p_diagonal_mode, (int)DIAGONAL_MODE_MAX); +	diagonal_mode = p_diagonal_mode; +} + +AStarGrid2D::DiagonalMode AStarGrid2D::get_diagonal_mode() const { +	return diagonal_mode; +} + +void AStarGrid2D::set_default_heuristic(Heuristic p_heuristic) { +	ERR_FAIL_INDEX((int)p_heuristic, (int)HEURISTIC_MAX); +	default_heuristic = p_heuristic; +} + +AStarGrid2D::Heuristic AStarGrid2D::get_default_heuristic() const { +	return default_heuristic; +} + +void AStarGrid2D::set_point_solid(const Vector2i &p_id, bool p_solid) { +	ERR_FAIL_COND_MSG(dirty, "Grid is not initialized. Call the update method."); +	ERR_FAIL_COND_MSG(!is_in_boundsv(p_id), vformat("Can't set if point is disabled. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height)); +	points[p_id.y][p_id.x].solid = p_solid; +} + +bool AStarGrid2D::is_point_solid(const Vector2i &p_id) const { +	ERR_FAIL_COND_V_MSG(dirty, false, "Grid is not initialized. Call the update method."); +	ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_id), false, vformat("Can't get if point is disabled. Point out of bounds (%s/%s, %s/%s).", p_id.x, size.width, p_id.y, size.height)); +	return points[p_id.y][p_id.x].solid; +} + +AStarGrid2D::Point *AStarGrid2D::_jump(Point *p_from, Point *p_to) { +	if (!p_to || p_to->solid) { +		return nullptr; +	} +	if (p_to == end) { +		return p_to; +	} + +	int64_t from_x = p_from->id.x; +	int64_t from_y = p_from->id.y; + +	int64_t to_x = p_to->id.x; +	int64_t to_y = p_to->id.y; + +	int64_t dx = to_x - from_x; +	int64_t dy = to_y - from_y; + +	if (diagonal_mode == DIAGONAL_MODE_ALWAYS || diagonal_mode == DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE) { +		if (dx != 0 && dy != 0) { +			if ((_is_walkable(to_x - dx, to_y + dy) && !_is_walkable(to_x - dx, to_y)) || (_is_walkable(to_x + dx, to_y - dy) && !_is_walkable(to_x, to_y - dy))) { +				return p_to; +			} +			if (_jump(p_to, _get_point(to_x + dx, to_y)) != nullptr) { +				return p_to; +			} +			if (_jump(p_to, _get_point(to_x, to_y + dy)) != nullptr) { +				return p_to; +			} +		} else { +			if (dx != 0) { +				if ((_is_walkable(to_x + dx, to_y + 1) && !_is_walkable(to_x, to_y + 1)) || (_is_walkable(to_x + dx, to_y - 1) && !_is_walkable(to_x, to_y - 1))) { +					return p_to; +				} +			} else { +				if ((_is_walkable(to_x + 1, to_y + dy) && !_is_walkable(to_x + 1, to_y)) || (_is_walkable(to_x - 1, to_y + dy) && !_is_walkable(to_x - 1, to_y))) { +					return p_to; +				} +			} +		} +		if (_is_walkable(to_x + dx, to_y + dy) && (diagonal_mode == DIAGONAL_MODE_ALWAYS || (_is_walkable(to_x + dx, to_y) || _is_walkable(to_x, to_y + dy)))) { +			return _jump(p_to, _get_point(to_x + dx, to_y + dy)); +		} +	} else if (diagonal_mode == DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES) { +		if (dx != 0 && dy != 0) { +			if ((_is_walkable(to_x + dx, to_y + dy) && !_is_walkable(to_x, to_y + dy)) || !_is_walkable(to_x + dx, to_y)) { +				return p_to; +			} +			if (_jump(p_to, _get_point(to_x + dx, to_y)) != nullptr) { +				return p_to; +			} +			if (_jump(p_to, _get_point(to_x, to_y + dy)) != nullptr) { +				return p_to; +			} +		} else { +			if (dx != 0) { +				if ((_is_walkable(to_x, to_y + 1) && !_is_walkable(to_x - dx, to_y + 1)) || (_is_walkable(to_x, to_y - 1) && !_is_walkable(to_x - dx, to_y - 1))) { +					return p_to; +				} +			} else { +				if ((_is_walkable(to_x + 1, to_y) && !_is_walkable(to_x + 1, to_y - dy)) || (_is_walkable(to_x - 1, to_y) && !_is_walkable(to_x - 1, to_y - dy))) { +					return p_to; +				} +			} +		} +		if (_is_walkable(to_x + dx, to_y + dy) && _is_walkable(to_x + dx, to_y) && _is_walkable(to_x, to_y + dy)) { +			return _jump(p_to, _get_point(to_x + dx, to_y + dy)); +		} +	} else { // DIAGONAL_MODE_NEVER +		if (dx != 0) { +			if (!_is_walkable(to_x + dx, to_y)) { +				return p_to; +			} +			if (_jump(p_to, _get_point(to_x, to_y + 1)) != nullptr) { +				return p_to; +			} +			if (_jump(p_to, _get_point(to_x, to_y - 1)) != nullptr) { +				return p_to; +			} +		} else { +			if (!_is_walkable(to_x, to_y + dy)) { +				return p_to; +			} +			if (_jump(p_to, _get_point(to_x + 1, to_y)) != nullptr) { +				return p_to; +			} +			if (_jump(p_to, _get_point(to_x - 1, to_y)) != nullptr) { +				return p_to; +			} +		} +		if (_is_walkable(to_x + dx, to_y + dy) && _is_walkable(to_x + dx, to_y) && _is_walkable(to_x, to_y + dy)) { +			return _jump(p_to, _get_point(to_x + dx, to_y + dy)); +		} +	} +	return nullptr; +} + +void AStarGrid2D::_get_nbors(Point *p_point, List<Point *> &r_nbors) { +	bool ts0 = false, td0 = false, +		 ts1 = false, td1 = false, +		 ts2 = false, td2 = false, +		 ts3 = false, td3 = false; + +	Point *left = nullptr; +	Point *right = nullptr; +	Point *top = nullptr; +	Point *bottom = nullptr; + +	Point *top_left = nullptr; +	Point *top_right = nullptr; +	Point *bottom_left = nullptr; +	Point *bottom_right = nullptr; + +	{ +		bool has_left = false; +		bool has_right = false; + +		if (p_point->id.x - 1 >= 0) { +			left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y); +			has_left = true; +		} +		if (p_point->id.x + 1 < size.width) { +			right = _get_point_unchecked(p_point->id.x + 1, p_point->id.y); +			has_right = true; +		} +		if (p_point->id.y - 1 >= 0) { +			top = _get_point_unchecked(p_point->id.x, p_point->id.y - 1); +			if (has_left) { +				top_left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y - 1); +			} +			if (has_right) { +				top_right = _get_point_unchecked(p_point->id.x + 1, p_point->id.y - 1); +			} +		} +		if (p_point->id.y + 1 < size.height) { +			bottom = _get_point_unchecked(p_point->id.x, p_point->id.y + 1); +			if (has_left) { +				bottom_left = _get_point_unchecked(p_point->id.x - 1, p_point->id.y + 1); +			} +			if (has_right) { +				bottom_right = _get_point_unchecked(p_point->id.x + 1, p_point->id.y + 1); +			} +		} +	} + +	if (top && !top->solid) { +		r_nbors.push_back(top); +		ts0 = true; +	} +	if (right && !right->solid) { +		r_nbors.push_back(right); +		ts1 = true; +	} +	if (bottom && !bottom->solid) { +		r_nbors.push_back(bottom); +		ts2 = true; +	} +	if (left && !left->solid) { +		r_nbors.push_back(left); +		ts3 = true; +	} + +	switch (diagonal_mode) { +		case DIAGONAL_MODE_ALWAYS: { +			td0 = true; +			td1 = true; +			td2 = true; +			td3 = true; +		} break; +		case DIAGONAL_MODE_NEVER: { +		} break; +		case DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE: { +			td0 = ts3 || ts0; +			td1 = ts0 || ts1; +			td2 = ts1 || ts2; +			td3 = ts2 || ts3; +		} break; +		case DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES: { +			td0 = ts3 && ts0; +			td1 = ts0 && ts1; +			td2 = ts1 && ts2; +			td3 = ts2 && ts3; +		} break; +		default: +			break; +	} + +	if (td0 && (top_left && !top_left->solid)) { +		r_nbors.push_back(top_left); +	} +	if (td1 && (top_right && !top_right->solid)) { +		r_nbors.push_back(top_right); +	} +	if (td2 && (bottom_right && !bottom_right->solid)) { +		r_nbors.push_back(bottom_right); +	} +	if (td3 && (bottom_left && !bottom_left->solid)) { +		r_nbors.push_back(bottom_left); +	} +} + +bool AStarGrid2D::_solve(Point *p_begin_point, Point *p_end_point) { +	pass++; + +	if (p_end_point->solid) { +		return false; +	} + +	bool found_route = false; + +	Vector<Point *> open_list; +	SortArray<Point *, SortPoints> sorter; + +	p_begin_point->g_score = 0; +	p_begin_point->f_score = _estimate_cost(p_begin_point->id, p_end_point->id); +	open_list.push_back(p_begin_point); +	end = p_end_point; + +	while (!open_list.is_empty()) { +		Point *p = open_list[0]; // The currently processed point. + +		if (p == p_end_point) { +			found_route = true; +			break; +		} + +		sorter.pop_heap(0, open_list.size(), open_list.ptrw()); // Remove the current point from the open list. +		open_list.remove_at(open_list.size() - 1); +		p->closed_pass = pass; // Mark the point as closed. + +		List<Point *> nbors; +		_get_nbors(p, nbors); +		for (List<Point *>::Element *E = nbors.front(); E; E = E->next()) { +			Point *e = E->get(); // The neighbour point. +			if (jumping_enabled) { +				e = _jump(p, e); +				if (!e || e->closed_pass == pass) { +					continue; +				} +			} else { +				if (e->solid || e->closed_pass == pass) { +					continue; +				} +			} + +			real_t tentative_g_score = p->g_score + _compute_cost(p->id, e->id); +			bool new_point = false; + +			if (e->open_pass != pass) { // The point wasn't inside the open list. +				e->open_pass = pass; +				open_list.push_back(e); +				new_point = true; +			} else if (tentative_g_score >= e->g_score) { // The new path is worse than the previous. +				continue; +			} + +			e->prev_point = p; +			e->g_score = tentative_g_score; +			e->f_score = e->g_score + _estimate_cost(e->id, p_end_point->id); + +			if (new_point) { // The position of the new points is already known. +				sorter.push_heap(0, open_list.size() - 1, 0, e, open_list.ptrw()); +			} else { +				sorter.push_heap(0, open_list.find(e), 0, e, open_list.ptrw()); +			} +		} +	} + +	return found_route; +} + +real_t AStarGrid2D::_estimate_cost(const Vector2i &p_from_id, const Vector2i &p_to_id) { +	real_t scost; +	if (GDVIRTUAL_CALL(_estimate_cost, p_from_id, p_to_id, scost)) { +		return scost; +	} +	return heuristics[default_heuristic](p_from_id, p_to_id); +} + +real_t AStarGrid2D::_compute_cost(const Vector2i &p_from_id, const Vector2i &p_to_id) { +	real_t scost; +	if (GDVIRTUAL_CALL(_compute_cost, p_from_id, p_to_id, scost)) { +		return scost; +	} +	return heuristics[default_heuristic](p_from_id, p_to_id); +} + +void AStarGrid2D::clear() { +	points.clear(); +	size = Vector2i(); +} + +Vector<Vector2> AStarGrid2D::get_point_path(const Vector2i &p_from_id, const Vector2i &p_to_id) { +	ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method."); +	ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height)); +	ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height)); + +	Point *a = _get_point(p_from_id.x, p_from_id.y); +	Point *b = _get_point(p_to_id.x, p_to_id.y); + +	if (a == b) { +		Vector<Vector2> ret; +		ret.push_back(a->pos); +		return ret; +	} + +	Point *begin_point = a; +	Point *end_point = b; + +	bool found_route = _solve(begin_point, end_point); +	if (!found_route) { +		return Vector<Vector2>(); +	} + +	Point *p = end_point; +	int64_t pc = 1; +	while (p != begin_point) { +		pc++; +		p = p->prev_point; +	} + +	Vector<Vector2> path; +	path.resize(pc); + +	{ +		Vector2 *w = path.ptrw(); + +		p = end_point; +		int64_t idx = pc - 1; +		while (p != begin_point) { +			w[idx--] = p->pos; +			p = p->prev_point; +		} + +		w[0] = p->pos; +	} + +	return path; +} + +Vector<Vector2> AStarGrid2D::get_id_path(const Vector2i &p_from_id, const Vector2i &p_to_id) { +	ERR_FAIL_COND_V_MSG(dirty, Vector<Vector2>(), "Grid is not initialized. Call the update method."); +	ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_from_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_from_id.x, size.width, p_from_id.y, size.height)); +	ERR_FAIL_COND_V_MSG(!is_in_boundsv(p_to_id), Vector<Vector2>(), vformat("Can't get id path. Point out of bounds (%s/%s, %s/%s)", p_to_id.x, size.width, p_to_id.y, size.height)); + +	Point *a = _get_point(p_from_id.x, p_from_id.y); +	Point *b = _get_point(p_to_id.x, p_to_id.y); + +	if (a == b) { +		Vector<Vector2> ret; +		ret.push_back(Vector2((float)a->id.x, (float)a->id.y)); +		return ret; +	} + +	Point *begin_point = a; +	Point *end_point = b; + +	bool found_route = _solve(begin_point, end_point); +	if (!found_route) { +		return Vector<Vector2>(); +	} + +	Point *p = end_point; +	int64_t pc = 1; +	while (p != begin_point) { +		pc++; +		p = p->prev_point; +	} + +	Vector<Vector2> path; +	path.resize(pc); + +	{ +		Vector2 *w = path.ptrw(); + +		p = end_point; +		int64_t idx = pc - 1; +		while (p != begin_point) { +			w[idx--] = Vector2((float)p->id.x, (float)p->id.y); +			p = p->prev_point; +		} + +		w[0] = p->id; +	} + +	return path; +} + +void AStarGrid2D::_bind_methods() { +	ClassDB::bind_method(D_METHOD("set_size", "size"), &AStarGrid2D::set_size); +	ClassDB::bind_method(D_METHOD("get_size"), &AStarGrid2D::get_size); +	ClassDB::bind_method(D_METHOD("set_offset", "offset"), &AStarGrid2D::set_offset); +	ClassDB::bind_method(D_METHOD("get_offset"), &AStarGrid2D::get_offset); +	ClassDB::bind_method(D_METHOD("set_cell_size", "cell_size"), &AStarGrid2D::set_cell_size); +	ClassDB::bind_method(D_METHOD("get_cell_size"), &AStarGrid2D::get_cell_size); +	ClassDB::bind_method(D_METHOD("is_in_bounds", "x", "y"), &AStarGrid2D::is_in_bounds); +	ClassDB::bind_method(D_METHOD("is_in_boundsv", "id"), &AStarGrid2D::is_in_boundsv); +	ClassDB::bind_method(D_METHOD("is_dirty"), &AStarGrid2D::is_dirty); +	ClassDB::bind_method(D_METHOD("update"), &AStarGrid2D::update); +	ClassDB::bind_method(D_METHOD("set_jumping_enabled", "enabled"), &AStarGrid2D::set_jumping_enabled); +	ClassDB::bind_method(D_METHOD("is_jumping_enabled"), &AStarGrid2D::is_jumping_enabled); +	ClassDB::bind_method(D_METHOD("set_diagonal_mode", "mode"), &AStarGrid2D::set_diagonal_mode); +	ClassDB::bind_method(D_METHOD("get_diagonal_mode"), &AStarGrid2D::get_diagonal_mode); +	ClassDB::bind_method(D_METHOD("set_default_heuristic", "heuristic"), &AStarGrid2D::set_default_heuristic); +	ClassDB::bind_method(D_METHOD("get_default_heuristic"), &AStarGrid2D::get_default_heuristic); +	ClassDB::bind_method(D_METHOD("set_point_solid", "id", "solid"), &AStarGrid2D::set_point_solid, DEFVAL(true)); +	ClassDB::bind_method(D_METHOD("is_point_solid", "id"), &AStarGrid2D::is_point_solid); +	ClassDB::bind_method(D_METHOD("clear"), &AStarGrid2D::clear); + +	ClassDB::bind_method(D_METHOD("get_point_path", "from_id", "to_id"), &AStarGrid2D::get_point_path); +	ClassDB::bind_method(D_METHOD("get_id_path", "from_id", "to_id"), &AStarGrid2D::get_id_path); + +	GDVIRTUAL_BIND(_estimate_cost, "from_id", "to_id") +	GDVIRTUAL_BIND(_compute_cost, "from_id", "to_id") + +	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2I, "size"), "set_size", "get_size"); +	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "offset"), "set_offset", "get_offset"); +	ADD_PROPERTY(PropertyInfo(Variant::VECTOR2, "cell_size"), "set_cell_size", "get_cell_size"); + +	ADD_PROPERTY(PropertyInfo(Variant::BOOL, "jumping_enabled"), "set_jumping_enabled", "is_jumping_enabled"); +	ADD_PROPERTY(PropertyInfo(Variant::INT, "default_heuristic", PROPERTY_HINT_ENUM, "Manhattan,Euclidean,Octile,Chebyshev,Max"), "set_default_heuristic", "get_default_heuristic"); +	ADD_PROPERTY(PropertyInfo(Variant::INT, "diagonal_mode", PROPERTY_HINT_ENUM, "Never,Always,At Least One Walkable,Only If No Obstacles,Max"), "set_diagonal_mode", "get_diagonal_mode"); + +	BIND_ENUM_CONSTANT(HEURISTIC_EUCLIDEAN); +	BIND_ENUM_CONSTANT(HEURISTIC_MANHATTAN); +	BIND_ENUM_CONSTANT(HEURISTIC_OCTILE); +	BIND_ENUM_CONSTANT(HEURISTIC_CHEBYSHEV); +	BIND_ENUM_CONSTANT(HEURISTIC_MAX); + +	BIND_ENUM_CONSTANT(DIAGONAL_MODE_ALWAYS); +	BIND_ENUM_CONSTANT(DIAGONAL_MODE_NEVER); +	BIND_ENUM_CONSTANT(DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE); +	BIND_ENUM_CONSTANT(DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES); +	BIND_ENUM_CONSTANT(DIAGONAL_MODE_MAX); +} diff --git a/core/math/a_star_grid_2d.h b/core/math/a_star_grid_2d.h new file mode 100644 index 0000000000..66312d10ac --- /dev/null +++ b/core/math/a_star_grid_2d.h @@ -0,0 +1,178 @@ +/*************************************************************************/ +/*  a_star_grid_2d.h                                                     */ +/*************************************************************************/ +/*                       This file is part of:                           */ +/*                           GODOT ENGINE                                */ +/*                      https://godotengine.org                          */ +/*************************************************************************/ +/* Copyright (c) 2007-2022 Juan Linietsky, Ariel Manzur.                 */ +/* Copyright (c) 2014-2022 Godot Engine contributors (cf. AUTHORS.md).   */ +/*                                                                       */ +/* 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.                */ +/*************************************************************************/ + +#ifndef A_STAR_GRID_2D_H +#define A_STAR_GRID_2D_H + +#include "core/object/gdvirtual.gen.inc" +#include "core/object/ref_counted.h" +#include "core/object/script_language.h" +#include "core/templates/list.h" +#include "core/templates/local_vector.h" + +class AStarGrid2D : public RefCounted { +	GDCLASS(AStarGrid2D, RefCounted); + +public: +	enum DiagonalMode { +		DIAGONAL_MODE_ALWAYS, +		DIAGONAL_MODE_NEVER, +		DIAGONAL_MODE_AT_LEAST_ONE_WALKABLE, +		DIAGONAL_MODE_ONLY_IF_NO_OBSTACLES, +		DIAGONAL_MODE_MAX, +	}; + +	enum Heuristic { +		HEURISTIC_EUCLIDEAN, +		HEURISTIC_MANHATTAN, +		HEURISTIC_OCTILE, +		HEURISTIC_CHEBYSHEV, +		HEURISTIC_MAX, +	}; + +private: +	Vector2i size; +	Vector2 offset; +	Vector2 cell_size = Vector2(1, 1); +	bool dirty = false; + +	bool jumping_enabled = false; +	DiagonalMode diagonal_mode = DIAGONAL_MODE_ALWAYS; +	Heuristic default_heuristic = HEURISTIC_EUCLIDEAN; + +	struct Point { +		Vector2i id; + +		bool solid = false; +		Vector2 pos; + +		// Used for pathfinding. +		Point *prev_point = nullptr; +		real_t g_score = 0; +		real_t f_score = 0; +		uint64_t open_pass = 0; +		uint64_t closed_pass = 0; + +		Point() {} + +		Point(const Vector2i &p_id, const Vector2 &p_pos) : +				id(p_id), pos(p_pos) {} +	}; + +	struct SortPoints { +		_FORCE_INLINE_ bool operator()(const Point *A, const Point *B) const { // Returns true when the Point A is worse than Point B. +			if (A->f_score > B->f_score) { +				return true; +			} else if (A->f_score < B->f_score) { +				return false; +			} else { +				return A->g_score < B->g_score; // If the f_costs are the same then prioritize the points that are further away from the start. +			} +		} +	}; + +	LocalVector<LocalVector<Point>> points; +	Point *end = nullptr; + +	uint64_t pass = 1; + +private: // Internal routines. +	_FORCE_INLINE_ bool _is_walkable(int64_t p_x, int64_t p_y) const { +		if (p_x >= 0 && p_y >= 0 && p_x < size.width && p_y < size.height) { +			return !points[p_y][p_x].solid; +		} +		return false; +	} + +	_FORCE_INLINE_ Point *_get_point(int64_t p_x, int64_t p_y) { +		if (p_x >= 0 && p_y >= 0 && p_x < size.width && p_y < size.height) { +			return &points[p_y][p_x]; +		} +		return nullptr; +	} + +	_FORCE_INLINE_ Point *_get_point_unchecked(int64_t p_x, int64_t p_y) { +		return &points[p_y][p_x]; +	} + +	void _get_nbors(Point *p_point, List<Point *> &r_nbors); +	Point *_jump(Point *p_from, Point *p_to); +	bool _solve(Point *p_begin_point, Point *p_end_point); + +protected: +	static void _bind_methods(); + +	virtual real_t _estimate_cost(const Vector2i &p_from_id, const Vector2i &p_to_id); +	virtual real_t _compute_cost(const Vector2i &p_from_id, const Vector2i &p_to_id); + +	GDVIRTUAL2RC(real_t, _estimate_cost, Vector2i, Vector2i) +	GDVIRTUAL2RC(real_t, _compute_cost, Vector2i, Vector2i) + +public: +	void set_size(const Vector2i &p_size); +	Vector2i get_size() const; + +	void set_offset(const Vector2 &p_offset); +	Vector2 get_offset() const; + +	void set_cell_size(const Vector2 &p_cell_size); +	Vector2 get_cell_size() const; + +	void update(); + +	int get_width() const; +	int get_height() const; + +	bool is_in_bounds(int p_x, int p_y) const; +	bool is_in_boundsv(const Vector2i &p_id) const; +	bool is_dirty() const; + +	void set_jumping_enabled(bool p_enabled); +	bool is_jumping_enabled() const; + +	void set_diagonal_mode(DiagonalMode p_diagonal_mode); +	DiagonalMode get_diagonal_mode() const; + +	void set_default_heuristic(Heuristic p_heuristic); +	Heuristic get_default_heuristic() const; + +	void set_point_solid(const Vector2i &p_id, bool p_solid = true); +	bool is_point_solid(const Vector2i &p_id) const; + +	void clear(); + +	Vector<Vector2> get_point_path(const Vector2i &p_from, const Vector2i &p_to); +	Vector<Vector2> get_id_path(const Vector2i &p_from, const Vector2i &p_to); +}; + +VARIANT_ENUM_CAST(AStarGrid2D::DiagonalMode); +VARIANT_ENUM_CAST(AStarGrid2D::Heuristic); + +#endif // A_STAR_GRID_2D_H diff --git a/core/math/transform_3d.cpp b/core/math/transform_3d.cpp index a634faca9a..2de9e81b38 100644 --- a/core/math/transform_3d.cpp +++ b/core/math/transform_3d.cpp @@ -94,9 +94,7 @@ void Transform3D::set_look_at(const Vector3 &p_eye, const Vector3 &p_target, con  	origin = p_eye;  } -Transform3D Transform3D::spherical_interpolate_with(const Transform3D &p_transform, real_t p_c) const { -	/* not sure if very "efficient" but good enough? */ - +Transform3D Transform3D::interpolate_with(const Transform3D &p_transform, real_t p_c) const {  	Transform3D interp;  	Vector3 src_scale = basis.get_scale(); @@ -113,15 +111,6 @@ Transform3D Transform3D::spherical_interpolate_with(const Transform3D &p_transfo  	return interp;  } -Transform3D Transform3D::interpolate_with(const Transform3D &p_transform, real_t p_c) const { -	Transform3D interp; - -	interp.basis = basis.lerp(p_transform.basis, p_c); -	interp.origin = origin.lerp(p_transform.origin, p_c); - -	return interp; -} -  void Transform3D::scale(const Vector3 &p_scale) {  	basis.scale(p_scale);  	origin *= p_scale; diff --git a/core/math/transform_3d.h b/core/math/transform_3d.h index b572e90859..c62e4a7b0e 100644 --- a/core/math/transform_3d.h +++ b/core/math/transform_3d.h @@ -103,7 +103,6 @@ struct _NO_DISCARD_ Transform3D {  	void operator*=(const real_t p_val);  	Transform3D operator*(const real_t p_val) const; -	Transform3D spherical_interpolate_with(const Transform3D &p_transform, real_t p_c) const;  	Transform3D interpolate_with(const Transform3D &p_transform, real_t p_c) const;  	_FORCE_INLINE_ Transform3D inverse_xform(const Transform3D &t) const { diff --git a/core/register_core_types.cpp b/core/register_core_types.cpp index 9ad6b0ca68..7f734201e7 100644 --- a/core/register_core_types.cpp +++ b/core/register_core_types.cpp @@ -64,6 +64,7 @@  #include "core/io/udp_server.h"  #include "core/io/xml_parser.h"  #include "core/math/a_star.h" +#include "core/math/a_star_grid_2d.h"  #include "core/math/expression.h"  #include "core/math/geometry_2d.h"  #include "core/math/geometry_3d.h" @@ -236,6 +237,7 @@ void register_core_types() {  	GDREGISTER_ABSTRACT_CLASS(PackedDataContainerRef);  	GDREGISTER_CLASS(AStar3D);  	GDREGISTER_CLASS(AStar2D); +	GDREGISTER_CLASS(AStarGrid2D);  	GDREGISTER_CLASS(EncodedObjectAsID);  	GDREGISTER_CLASS(RandomNumberGenerator); diff --git a/core/string/ustring.cpp b/core/string/ustring.cpp index 0c43ba9ccc..d8b93998af 100644 --- a/core/string/ustring.cpp +++ b/core/string/ustring.cpp @@ -970,62 +970,71 @@ const char32_t *String::get_data() const {  	return size() ? &operator[](0) : &zero;  } -String String::capitalize() const { -	String aux = this->camelcase_to_underscore(true).replace("_", " ").strip_edges(); -	String cap; -	for (int i = 0; i < aux.get_slice_count(" "); i++) { -		String slice = aux.get_slicec(' ', i); -		if (slice.length() > 0) { -			slice[0] = _find_upper(slice[0]); -			if (i > 0) { -				cap += " "; -			} -			cap += slice; -		} -	} - -	return cap; -} - -String String::camelcase_to_underscore(bool lowercase) const { +String String::_camelcase_to_underscore() const {  	const char32_t *cstr = get_data();  	String new_string;  	int start_index = 0;  	for (int i = 1; i < this->size(); i++) { -		bool is_upper = is_ascii_upper_case(cstr[i]); -		bool is_number = is_digit(cstr[i]); +		bool is_prev_upper = is_ascii_upper_case(cstr[i - 1]); +		bool is_prev_lower = is_ascii_lower_case(cstr[i - 1]); +		bool is_prev_digit = is_digit(cstr[i - 1]); -		bool are_next_2_lower = false; -		bool is_next_lower = false; -		bool is_next_number = false; -		bool was_precedent_upper = is_ascii_upper_case(cstr[i - 1]); -		bool was_precedent_number = is_digit(cstr[i - 1]); - -		if (i + 2 < this->size()) { -			are_next_2_lower = is_ascii_lower_case(cstr[i + 1]) && is_ascii_lower_case(cstr[i + 2]); -		} +		bool is_curr_upper = is_ascii_upper_case(cstr[i]); +		bool is_curr_lower = is_ascii_lower_case(cstr[i]); +		bool is_curr_digit = is_digit(cstr[i]); +		bool is_next_lower = false;  		if (i + 1 < this->size()) {  			is_next_lower = is_ascii_lower_case(cstr[i + 1]); -			is_next_number = is_digit(cstr[i + 1]);  		} -		const bool cond_a = is_upper && !was_precedent_upper && !was_precedent_number; -		const bool cond_b = was_precedent_upper && is_upper && are_next_2_lower; -		const bool cond_c = is_number && !was_precedent_number; -		const bool can_break_number_letter = is_number && !was_precedent_number && is_next_lower; -		const bool can_break_letter_number = !is_number && was_precedent_number && (is_next_lower || is_next_number); +		const bool cond_a = is_prev_lower && is_curr_upper; // aA +		const bool cond_b = (is_prev_upper || is_prev_digit) && is_curr_upper && is_next_lower; // AAa, 2Aa +		const bool cond_c = is_prev_digit && is_curr_lower && is_next_lower; // 2aa +		const bool cond_d = (is_prev_upper || is_prev_lower) && is_curr_digit; // A2, a2 -		bool should_split = cond_a || cond_b || cond_c || can_break_number_letter || can_break_letter_number; -		if (should_split) { +		if (cond_a || cond_b || cond_c || cond_d) {  			new_string += this->substr(start_index, i - start_index) + "_";  			start_index = i;  		}  	}  	new_string += this->substr(start_index, this->size() - start_index); -	return lowercase ? new_string.to_lower() : new_string; +	return new_string.to_lower(); +} + +String String::capitalize() const { +	String aux = this->_camelcase_to_underscore().replace("_", " ").strip_edges(); +	String cap; +	for (int i = 0; i < aux.get_slice_count(" "); i++) { +		String slice = aux.get_slicec(' ', i); +		if (slice.length() > 0) { +			slice[0] = _find_upper(slice[0]); +			if (i > 0) { +				cap += " "; +			} +			cap += slice; +		} +	} + +	return cap; +} + +String String::to_camel_case() const { +	String s = this->to_pascal_case(); +	if (!s.is_empty()) { +		s[0] = _find_lower(s[0]); +	} +	return s; +} + +String String::to_pascal_case() const { +	return this->capitalize().replace(" ", ""); +} + +String String::to_snake_case() const { +	return this->_camelcase_to_underscore().replace(" ", "_").strip_edges();  }  String String::get_with_code_lines() const { @@ -4451,7 +4460,7 @@ String String::get_extension() const {  	return substr(pos + 1, length());  } -String String::plus_file(const String &p_file) const { +String String::path_join(const String &p_file) const {  	if (is_empty()) {  		return p_file;  	} diff --git a/core/string/ustring.h b/core/string/ustring.h index 6c3169f136..31de7cc464 100644 --- a/core/string/ustring.h +++ b/core/string/ustring.h @@ -196,6 +196,7 @@ class String {  	bool _base_is_subsequence_of(const String &p_string, bool case_insensitive) const;  	int _count(const String &p_string, int p_from, int p_to, bool p_case_insensitive) const; +	String _camelcase_to_underscore() const;  public:  	enum { @@ -335,7 +336,9 @@ public:  	static double to_float(const char32_t *p_str, const char32_t **r_end = nullptr);  	String capitalize() const; -	String camelcase_to_underscore(bool lowercase = true) const; +	String to_camel_case() const; +	String to_pascal_case() const; +	String to_snake_case() const;  	String get_with_code_lines() const;  	int get_slice_count(String p_splitter) const; @@ -370,7 +373,7 @@ public:  	String rstrip(const String &p_chars) const;  	String get_extension() const;  	String get_basename() const; -	String plus_file(const String &p_file) const; +	String path_join(const String &p_file) const;  	char32_t unicode_at(int p_idx) const;  	void erase(int p_pos, int p_chars); diff --git a/core/variant/variant_call.cpp b/core/variant/variant_call.cpp index 9b7dc5012b..a4bce979fe 100644 --- a/core/variant/variant_call.cpp +++ b/core/variant/variant_call.cpp @@ -1506,6 +1506,9 @@ static void _register_variant_builtin_methods() {  	bind_method(String, repeat, sarray("count"), varray());  	bind_method(String, insert, sarray("position", "what"), varray());  	bind_method(String, capitalize, sarray(), varray()); +	bind_method(String, to_camel_case, sarray(), varray()); +	bind_method(String, to_pascal_case, sarray(), varray()); +	bind_method(String, to_snake_case, sarray(), varray());  	bind_method(String, split, sarray("delimiter", "allow_empty", "maxsplit"), varray(true, 0));  	bind_method(String, rsplit, sarray("delimiter", "allow_empty", "maxsplit"), varray(true, 0));  	bind_method(String, split_floats, sarray("delimiter", "allow_empty"), varray(true)); @@ -1523,7 +1526,7 @@ static void _register_variant_builtin_methods() {  	bind_method(String, rstrip, sarray("chars"), varray());  	bind_method(String, get_extension, sarray(), varray());  	bind_method(String, get_basename, sarray(), varray()); -	bind_method(String, plus_file, sarray("file"), varray()); +	bind_method(String, path_join, sarray("file"), varray());  	bind_method(String, unicode_at, sarray("at"), varray());  	bind_method(String, indent, sarray("prefix"), varray());  	bind_method(String, dedent, sarray(), varray()); @@ -1966,7 +1969,6 @@ static void _register_variant_builtin_methods() {  	bind_method(Transform3D, translated, sarray("offset"), varray());  	bind_method(Transform3D, translated_local, sarray("offset"), varray());  	bind_method(Transform3D, looking_at, sarray("target", "up"), varray(Vector3(0, 1, 0))); -	bind_method(Transform3D, spherical_interpolate_with, sarray("xform", "weight"), varray());  	bind_method(Transform3D, interpolate_with, sarray("xform", "weight"), varray());  	bind_method(Transform3D, is_equal_approx, sarray("xform"), varray());  |