diff options
Diffstat (limited to 'modules')
25 files changed, 449 insertions, 289 deletions
diff --git a/modules/gdscript/editor/gdscript_highlighter.cpp b/modules/gdscript/editor/gdscript_highlighter.cpp index 95fc9b34a0..02922086f0 100644 --- a/modules/gdscript/editor/gdscript_highlighter.cpp +++ b/modules/gdscript/editor/gdscript_highlighter.cpp @@ -279,23 +279,24 @@ Dictionary GDScriptSyntaxHighlighter::_get_line_syntax_highlighting_impl(int p_l in_number = true; } - // Special cases for numbers: Unary operators, separator '_', decimal point '.', literals '0x' and '0b', and scientific notation 'e'. + // Special cases for numbers if (in_number && !is_a_digit) { - if ((str[j] == '+' || str[j] == '-') && j > 0 && str[j - 1] == 'e' && !prev_is_digit) { - in_number = true; - } else if ((str[j] == 'e' || str[j] == '_' || str[j] == '.') && prev_is_digit) { - in_number = true; - } else if ((str[j] == 'b' || str[j] == 'x') && (j > 0 && str[j - 1] == '0')) { - in_number = true; - if (str[j] == 'b') { - is_bin_notation = true; - } else if (str[j] == 'x') { - is_hex_notation = true; - } - } else { + if (str[j] == 'b' && str[j - 1] == '0') { + is_bin_notation = true; + } else if (str[j] == 'x' && str[j - 1] == '0') { + is_hex_notation = true; + } else if (!((str[j] == '-' || str[j] == '+') && str[j - 1] == 'e' && !prev_is_digit) && + !(str[j] == '_' && (prev_is_digit || str[j - 1] == 'b' || str[j - 1] == 'x' || str[j - 1] == '.')) && + !((str[j] == 'e' || str[j] == '.') && (prev_is_digit || str[j - 1] == '_')) && + !((str[j] == '-' || str[j] == '+' || str[j] == '~') && !prev_is_binary_op && str[j - 1] != 'e')) { + /* 1st row of condition: '+' or '-' after scientific notation; + 2nd row of condition: '_' as a numeric separator; + 3rd row of condition: Scientific notation 'e' and floating points; + 4th row of condition: Multiple unary operators. */ in_number = false; } - } else if ((str[j] == '.' || str[j] == '+' || str[j] == '-' || str[j] == '~') && !is_binary_op) { + } else if ((str[j] == '-' || str[j] == '+' || str[j] == '~' || (str[j] == '.' && str[j + 1] != '.' && (j == 0 || (j > 0 && str[j - 1] != '.')))) && !is_binary_op) { + // Start a number from unary mathematical operators and floating points, except for '..' in_number = true; } diff --git a/modules/gdscript/gdscript.cpp b/modules/gdscript/gdscript.cpp index cf2d6ae9f8..10babad378 100644 --- a/modules/gdscript/gdscript.cpp +++ b/modules/gdscript/gdscript.cpp @@ -683,7 +683,7 @@ bool GDScript::_update_exports(bool *r_err, bool p_recursive_call, PlaceHolderSc if (base.is_empty() || base.is_relative_path()) { ERR_PRINT(("Could not resolve relative path for parent class: " + path).utf8().get_data()); } else { - path = base.get_base_dir().plus_file(path); + path = base.get_base_dir().path_join(path); } } } else if (c->extends.size() != 0) { @@ -2204,7 +2204,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b if (c->icon_path.is_empty() || c->icon_path.is_absolute_path()) { *r_icon_path = c->icon_path; } else if (c->icon_path.is_relative_path()) { - *r_icon_path = p_path.get_base_dir().plus_file(c->icon_path).simplify_path(); + *r_icon_path = p_path.get_base_dir().path_join(c->icon_path).simplify_path(); } } if (r_base_type) { @@ -2232,7 +2232,7 @@ String GDScriptLanguage::get_global_class_name(const String &p_path, String *r_b } String subpath = subclass->extends_path; if (subpath.is_relative_path()) { - subpath = path.get_base_dir().plus_file(subpath).simplify_path(); + subpath = path.get_base_dir().path_join(subpath).simplify_path(); } if (OK != subparser.parse(subsource, subpath, false)) { diff --git a/modules/gdscript/gdscript_analyzer.cpp b/modules/gdscript/gdscript_analyzer.cpp index a07d4855f3..c8c876369f 100644 --- a/modules/gdscript/gdscript_analyzer.cpp +++ b/modules/gdscript/gdscript_analyzer.cpp @@ -260,7 +260,7 @@ Error GDScriptAnalyzer::resolve_inheritance(GDScriptParser::ClassNode *p_class, if (!p_class->extends_path.is_empty()) { if (p_class->extends_path.is_relative_path()) { - p_class->extends_path = class_type.script_path.get_base_dir().plus_file(p_class->extends_path).simplify_path(); + p_class->extends_path = class_type.script_path.get_base_dir().path_join(p_class->extends_path).simplify_path(); } Ref<GDScriptParserRef> parser = get_parser_for(p_class->extends_path); if (parser.is_null()) { @@ -3185,7 +3185,7 @@ void GDScriptAnalyzer::reduce_preload(GDScriptParser::PreloadNode *p_preload) { p_preload->resolved_path = p_preload->path->reduced_value; // TODO: Save this as script dependency. if (p_preload->resolved_path.is_relative_path()) { - p_preload->resolved_path = parser->script_path.get_base_dir().plus_file(p_preload->resolved_path); + p_preload->resolved_path = parser->script_path.get_base_dir().path_join(p_preload->resolved_path); } p_preload->resolved_path = p_preload->resolved_path.simplify_path(); if (!FileAccess::exists(p_preload->resolved_path)) { diff --git a/modules/gdscript/language_server/gdscript_workspace.cpp b/modules/gdscript/language_server/gdscript_workspace.cpp index 44b60369ab..fd213e7b37 100644 --- a/modules/gdscript/language_server/gdscript_workspace.cpp +++ b/modules/gdscript/language_server/gdscript_workspace.cpp @@ -228,9 +228,9 @@ void GDScriptWorkspace::list_script_files(const String &p_root_dir, List<String> String file_name = dir->get_next(); while (file_name.length()) { if (dir->current_is_dir() && file_name != "." && file_name != ".." && file_name != "./") { - list_script_files(p_root_dir.plus_file(file_name), r_files); + list_script_files(p_root_dir.path_join(file_name), r_files); } else if (file_name.ends_with(".gd")) { - String script_file = p_root_dir.plus_file(file_name); + String script_file = p_root_dir.path_join(file_name); r_files.push_back(script_file); } file_name = dir->get_next(); diff --git a/modules/gdscript/tests/gdscript_test_runner.cpp b/modules/gdscript/tests/gdscript_test_runner.cpp index e3b956369d..6c346acb7e 100644 --- a/modules/gdscript/tests/gdscript_test_runner.cpp +++ b/modules/gdscript/tests/gdscript_test_runner.cpp @@ -247,7 +247,7 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { next = dir->get_next(); continue; } - if (!make_tests_for_dir(current_dir.plus_file(next))) { + if (!make_tests_for_dir(current_dir.path_join(next))) { return false; } } else { @@ -255,7 +255,7 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { #ifndef DEBUG_ENABLED // On release builds, skip tests marked as debug only. Error open_err = OK; - Ref<FileAccess> script_file(FileAccess::open(current_dir.plus_file(next), FileAccess::READ, &open_err)); + Ref<FileAccess> script_file(FileAccess::open(current_dir.path_join(next), FileAccess::READ, &open_err)); if (open_err != OK) { ERR_PRINT(vformat(R"(Couldn't open test file "%s".)", next)); next = dir->get_next(); @@ -272,7 +272,7 @@ bool GDScriptTestRunner::make_tests_for_dir(const String &p_dir) { if (!is_generating && !dir->file_exists(out_file)) { ERR_FAIL_V_MSG(false, "Could not find output file for " + next); } - GDScriptTest test(current_dir.plus_file(next), current_dir.plus_file(out_file), source_dir); + GDScriptTest test(current_dir.path_join(next), current_dir.path_join(out_file), source_dir); tests.push_back(test); } } diff --git a/modules/gltf/doc_classes/GLTFCamera.xml b/modules/gltf/doc_classes/GLTFCamera.xml index b90abd105d..49efaa1564 100644 --- a/modules/gltf/doc_classes/GLTFCamera.xml +++ b/modules/gltf/doc_classes/GLTFCamera.xml @@ -10,6 +10,34 @@ <link title="GLTF camera detailed specification">https://registry.khronos.org/glTF/specs/2.0/glTF-2.0.html#reference-camera</link> <link title="GLTF camera spec and example file">https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_015_SimpleCameras.md</link> </tutorials> + <methods> + <method name="from_dictionary" qualifiers="static"> + <return type="GLTFCamera" /> + <param index="0" name="dictionary" type="Dictionary" /> + <description> + Creates a new GLTFCamera instance by parsing the given [Dictionary]. + </description> + </method> + <method name="from_node" qualifiers="static"> + <return type="GLTFCamera" /> + <param index="0" name="camera_node" type="Camera3D" /> + <description> + Create a new GLTFCamera instance from the given Godot [Camera3D] node. + </description> + </method> + <method name="to_dictionary" qualifiers="const"> + <return type="Dictionary" /> + <description> + Serializes this GLTFCamera instance into a [Dictionary]. + </description> + </method> + <method name="to_node" qualifiers="const"> + <return type="Camera3D" /> + <description> + Converts this GLTFCamera instance into a Godot [Camera3D] node. + </description> + </method> + </methods> <members> <member name="depth_far" type="float" setter="set_depth_far" getter="get_depth_far" default="4000.0"> The distance to the far culling boundary for this camera relative to its local Z axis, in meters. This maps to GLTF's [code]zfar[/code] property. diff --git a/modules/gltf/doc_classes/GLTFLight.xml b/modules/gltf/doc_classes/GLTFLight.xml index db2dfb487a..7fd59e14bc 100644 --- a/modules/gltf/doc_classes/GLTFLight.xml +++ b/modules/gltf/doc_classes/GLTFLight.xml @@ -9,6 +9,34 @@ <tutorials> <link title="KHR_lights_punctual GLTF extension spec">https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_lights_punctual</link> </tutorials> + <methods> + <method name="from_dictionary" qualifiers="static"> + <return type="GLTFLight" /> + <param index="0" name="dictionary" type="Dictionary" /> + <description> + Creates a new GLTFLight instance by parsing the given [Dictionary]. + </description> + </method> + <method name="from_node" qualifiers="static"> + <return type="GLTFLight" /> + <param index="0" name="light_node" type="Light3D" /> + <description> + Create a new GLTFLight instance from the given Godot [Light3D] node. + </description> + </method> + <method name="to_dictionary" qualifiers="const"> + <return type="Dictionary" /> + <description> + Serializes this GLTFLight instance into a [Dictionary]. + </description> + </method> + <method name="to_node" qualifiers="const"> + <return type="Light3D" /> + <description> + Converts this GLTFLight instance into a Godot [Light3D] node. + </description> + </method> + </methods> <members> <member name="color" type="Color" setter="set_color" getter="get_color" default="Color(1, 1, 1, 1)"> The [Color] of the light. Defaults to white. A black color causes the light to have no effect. diff --git a/modules/gltf/editor/editor_scene_importer_blend.cpp b/modules/gltf/editor/editor_scene_importer_blend.cpp index 707769da35..ab52761e17 100644 --- a/modules/gltf/editor/editor_scene_importer_blend.cpp +++ b/modules/gltf/editor/editor_scene_importer_blend.cpp @@ -64,7 +64,7 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ // Escape paths to be valid Python strings to embed in the script. const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape(); - const String sink = ProjectSettings::get_singleton()->get_imported_files_path().plus_file( + const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( vformat("%s-%s.gltf", p_path.get_file().get_basename(), p_path.md5_text())); const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape(); @@ -193,9 +193,9 @@ Node *EditorSceneFormatImporterBlend::import_scene(const String &p_path, uint32_ String blender_path = EDITOR_GET("filesystem/import/blender/blender3_path"); #ifdef WINDOWS_ENABLED - blender_path = blender_path.plus_file("blender.exe"); + blender_path = blender_path.path_join("blender.exe"); #else - blender_path = blender_path.plus_file("blender"); + blender_path = blender_path.path_join("blender"); #endif List<String> args; @@ -287,14 +287,14 @@ void EditorSceneFormatImporterBlend::get_import_options(const String &p_path, Li static bool _test_blender_path(const String &p_path, String *r_err = nullptr) { String path = p_path; #ifdef WINDOWS_ENABLED - path = path.plus_file("blender.exe"); + path = path.path_join("blender.exe"); #else - path = path.plus_file("blender"); + path = path.path_join("blender"); #endif #if defined(MACOS_ENABLED) if (!FileAccess::exists(path)) { - path = path.plus_file("Blender"); + path = path.path_join("Blender"); } #endif @@ -485,7 +485,7 @@ bool EditorFileSystemImportFormatSupportQueryBlend::query() { bool found = false; for (const String &path : mdfind_paths) { - found = _autodetect_path(path.plus_file("Contents/MacOS")); + found = _autodetect_path(path.path_join("Contents/MacOS")); if (found) { break; } diff --git a/modules/gltf/editor/editor_scene_importer_fbx.cpp b/modules/gltf/editor/editor_scene_importer_fbx.cpp index faad2d315d..017a44cccf 100644 --- a/modules/gltf/editor/editor_scene_importer_fbx.cpp +++ b/modules/gltf/editor/editor_scene_importer_fbx.cpp @@ -57,7 +57,7 @@ Node *EditorSceneFormatImporterFBX::import_scene(const String &p_path, uint32_t // enclosed in double quotes by OS::execute(), so we only need to escape those. // `c_escape_multiline()` seems to do this (escapes `\` and `"` only). const String source_global = ProjectSettings::get_singleton()->globalize_path(p_path).c_escape_multiline(); - const String sink = ProjectSettings::get_singleton()->get_imported_files_path().plus_file( + const String sink = ProjectSettings::get_singleton()->get_imported_files_path().path_join( vformat("%s-%s.glb", p_path.get_file().get_basename(), p_path.md5_text())); const String sink_global = ProjectSettings::get_singleton()->globalize_path(sink).c_escape_multiline(); diff --git a/modules/gltf/extensions/gltf_light.cpp b/modules/gltf/extensions/gltf_light.cpp index af21a4e804..ab5a15c671 100644 --- a/modules/gltf/extensions/gltf_light.cpp +++ b/modules/gltf/extensions/gltf_light.cpp @@ -31,6 +31,12 @@ #include "gltf_light.h" void GLTFLight::_bind_methods() { + ClassDB::bind_static_method("GLTFLight", D_METHOD("from_node", "light_node"), &GLTFLight::from_node); + ClassDB::bind_method(D_METHOD("to_node"), &GLTFLight::to_node); + + ClassDB::bind_static_method("GLTFLight", D_METHOD("from_dictionary", "dictionary"), &GLTFLight::from_dictionary); + ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFLight::to_dictionary); + ClassDB::bind_method(D_METHOD("get_color"), &GLTFLight::get_color); ClassDB::bind_method(D_METHOD("set_color", "color"), &GLTFLight::set_color); ClassDB::bind_method(D_METHOD("get_intensity"), &GLTFLight::get_intensity); @@ -99,3 +105,116 @@ float GLTFLight::get_outer_cone_angle() { void GLTFLight::set_outer_cone_angle(float p_outer_cone_angle) { outer_cone_angle = p_outer_cone_angle; } + +Ref<GLTFLight> GLTFLight::from_node(const Light3D *p_light) { + Ref<GLTFLight> l; + l.instantiate(); + l->color = p_light->get_color(); + if (cast_to<DirectionalLight3D>(p_light)) { + l->light_type = "directional"; + const DirectionalLight3D *light = cast_to<const DirectionalLight3D>(p_light); + l->intensity = light->get_param(DirectionalLight3D::PARAM_ENERGY); + l->range = FLT_MAX; // Range for directional lights is infinite in Godot. + } else if (cast_to<const OmniLight3D>(p_light)) { + l->light_type = "point"; + const OmniLight3D *light = cast_to<const OmniLight3D>(p_light); + l->range = light->get_param(OmniLight3D::PARAM_RANGE); + l->intensity = light->get_param(OmniLight3D::PARAM_ENERGY); + } else if (cast_to<const SpotLight3D>(p_light)) { + l->light_type = "spot"; + const SpotLight3D *light = cast_to<const SpotLight3D>(p_light); + l->range = light->get_param(SpotLight3D::PARAM_RANGE); + l->intensity = light->get_param(SpotLight3D::PARAM_ENERGY); + l->outer_cone_angle = Math::deg_to_rad(light->get_param(SpotLight3D::PARAM_SPOT_ANGLE)); + // This equation is the inverse of the import equation (which has a desmos link). + float angle_ratio = 1 - (0.2 / (0.1 + light->get_param(SpotLight3D::PARAM_SPOT_ATTENUATION))); + angle_ratio = MAX(0, angle_ratio); + l->inner_cone_angle = l->outer_cone_angle * angle_ratio; + } + return l; +} + +Light3D *GLTFLight::to_node() const { + if (light_type == "directional") { + DirectionalLight3D *light = memnew(DirectionalLight3D); + light->set_param(Light3D::PARAM_ENERGY, intensity); + light->set_color(color); + return light; + } + const float range = CLAMP(this->range, 0, 4096); + if (light_type == "point") { + OmniLight3D *light = memnew(OmniLight3D); + light->set_param(OmniLight3D::PARAM_ENERGY, intensity); + light->set_param(OmniLight3D::PARAM_RANGE, range); + light->set_color(color); + return light; + } + if (light_type == "spot") { + SpotLight3D *light = memnew(SpotLight3D); + light->set_param(SpotLight3D::PARAM_ENERGY, intensity); + light->set_param(SpotLight3D::PARAM_RANGE, range); + light->set_param(SpotLight3D::PARAM_SPOT_ANGLE, Math::rad_to_deg(outer_cone_angle)); + light->set_color(color); + // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b + // The points in desmos are not exact, except for (1, infinity). + float angle_ratio = inner_cone_angle / outer_cone_angle; + float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1; + light->set_param(SpotLight3D::PARAM_SPOT_ATTENUATION, angle_attenuation); + return light; + } + return memnew(Light3D); +} + +Ref<GLTFLight> GLTFLight::from_dictionary(const Dictionary p_dictionary) { + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFLight>(), "Failed to parse GLTF light, missing required field 'type'."); + Ref<GLTFLight> light; + light.instantiate(); + const String &type = p_dictionary["type"]; + light->light_type = type; + + if (p_dictionary.has("color")) { + const Array &arr = p_dictionary["color"]; + if (arr.size() == 3) { + light->color = Color(arr[0], arr[1], arr[2]).linear_to_srgb(); + } else { + ERR_PRINT("Error parsing GLTF light: The color must have exactly 3 numbers."); + } + } + if (p_dictionary.has("intensity")) { + light->intensity = p_dictionary["intensity"]; + } + if (p_dictionary.has("range")) { + light->range = p_dictionary["range"]; + } + if (type == "spot") { + const Dictionary &spot = p_dictionary["spot"]; + light->inner_cone_angle = spot["innerConeAngle"]; + light->outer_cone_angle = spot["outerConeAngle"]; + if (light->inner_cone_angle >= light->outer_cone_angle) { + ERR_PRINT("Error parsing GLTF light: The inner angle must be smaller than the outer angle."); + } + } else if (type != "point" && type != "directional") { + ERR_PRINT("Error parsing GLTF light: Light type '" + type + "' is unknown."); + } + return light; +} + +Dictionary GLTFLight::to_dictionary() const { + Dictionary d; + Array color_array; + color_array.resize(3); + color_array[0] = color.r; + color_array[1] = color.g; + color_array[2] = color.b; + d["color"] = color_array; + d["type"] = light_type; + if (light_type == "spot") { + Dictionary spot_dict; + spot_dict["innerConeAngle"] = inner_cone_angle; + spot_dict["outerConeAngle"] = outer_cone_angle; + d["spot"] = spot_dict; + } + d["intensity"] = intensity; + d["range"] = range; + return d; +} diff --git a/modules/gltf/extensions/gltf_light.h b/modules/gltf/extensions/gltf_light.h index f0765a1bbc..04980e144c 100644 --- a/modules/gltf/extensions/gltf_light.h +++ b/modules/gltf/extensions/gltf_light.h @@ -70,6 +70,12 @@ public: float get_outer_cone_angle(); void set_outer_cone_angle(float p_outer_cone_angle); + + static Ref<GLTFLight> from_node(const Light3D *p_light); + Light3D *to_node() const; + + static Ref<GLTFLight> from_dictionary(const Dictionary p_dictionary); + Dictionary to_dictionary() const; }; #endif // GLTF_LIGHT_H diff --git a/modules/gltf/gltf_document.cpp b/modules/gltf/gltf_document.cpp index 87ba1d9869..0ed212e21f 100644 --- a/modules/gltf/gltf_document.cpp +++ b/modules/gltf/gltf_document.cpp @@ -786,7 +786,7 @@ Error GLTFDocument::_parse_buffers(Ref<GLTFState> state, const String &p_base_pa } else { // Relative path to an external image file. ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); - uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. + uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. buffer_data = FileAccess::get_file_as_array(uri); ERR_FAIL_COND_V_MSG(buffer.size() == 0, ERR_PARSE_ERROR, "glTF: Couldn't load binary file as an array: " + uri); } @@ -3039,8 +3039,8 @@ Error GLTFDocument::_serialize_images(Ref<GLTFState> state, const String &p_path if (!da->dir_exists(new_texture_dir)) { da->make_dir(new_texture_dir); } - image->save_png(new_texture_dir.plus_file(name)); - d["uri"] = texture_dir.plus_file(name).uri_encode(); + image->save_png(new_texture_dir.path_join(name)); + d["uri"] = texture_dir.path_join(name).uri_encode(); } images.push_back(d); } @@ -3118,7 +3118,7 @@ Error GLTFDocument::_parse_images(Ref<GLTFState> state, const String &p_base_pat } else { // Relative path to an external image file. ERR_FAIL_COND_V(p_base_path.is_empty(), ERR_INVALID_PARAMETER); uri = uri.uri_decode(); - uri = p_base_path.plus_file(uri).replace("\\", "/"); // Fix for Windows. + uri = p_base_path.path_join(uri).replace("\\", "/"); // Fix for Windows. // ResourceLoader will rely on the file extension to use the relevant loader. // The spec says that if mimeType is defined, it should take precedence (e.g. // there could be a `.png` image which is actually JPEG), but there's no easy @@ -4534,28 +4534,7 @@ Error GLTFDocument::_serialize_lights(Ref<GLTFState> state) { } Array lights; for (GLTFLightIndex i = 0; i < state->lights.size(); i++) { - Dictionary d; - Ref<GLTFLight> light = state->lights[i]; - Array color; - color.resize(3); - color[0] = light->color.r; - color[1] = light->color.g; - color[2] = light->color.b; - d["color"] = color; - d["type"] = light->light_type; - if (light->light_type == "spot") { - Dictionary s; - float inner_cone_angle = light->inner_cone_angle; - s["innerConeAngle"] = inner_cone_angle; - float outer_cone_angle = light->outer_cone_angle; - s["outerConeAngle"] = outer_cone_angle; - d["spot"] = s; - } - float intensity = light->intensity; - d["intensity"] = intensity; - float range = light->range; - d["range"] = range; - lights.push_back(d); + lights.push_back(state->lights[i]->to_dictionary()); } Dictionary extensions; @@ -4577,27 +4556,7 @@ Error GLTFDocument::_serialize_cameras(Ref<GLTFState> state) { Array cameras; cameras.resize(state->cameras.size()); for (GLTFCameraIndex i = 0; i < state->cameras.size(); i++) { - Dictionary d; - - Ref<GLTFCamera> camera = state->cameras[i]; - - if (camera->get_perspective()) { - Dictionary persp; - persp["yfov"] = camera->get_fov(); - persp["zfar"] = camera->get_depth_far(); - persp["znear"] = camera->get_depth_near(); - d["perspective"] = persp; - d["type"] = "perspective"; - } else { - Dictionary ortho; - ortho["ymag"] = camera->get_size_mag(); - ortho["xmag"] = camera->get_size_mag(); - ortho["zfar"] = camera->get_depth_far(); - ortho["znear"] = camera->get_depth_near(); - d["orthographic"] = ortho; - d["type"] = "orthographic"; - } - cameras[i] = d; + cameras[i] = state->cameras[i]->to_dictionary(); } if (!state->cameras.size()) { @@ -4627,35 +4586,10 @@ Error GLTFDocument::_parse_lights(Ref<GLTFState> state) { const Array &lights = lights_punctual["lights"]; for (GLTFLightIndex light_i = 0; light_i < lights.size(); light_i++) { - const Dictionary &d = lights[light_i]; - - Ref<GLTFLight> light; - light.instantiate(); - ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - const String &type = d["type"]; - light->light_type = type; - - if (d.has("color")) { - const Array &arr = d["color"]; - ERR_FAIL_COND_V(arr.size() != 3, ERR_PARSE_ERROR); - const Color c = Color(arr[0], arr[1], arr[2]).linear_to_srgb(); - light->color = c; - } - if (d.has("intensity")) { - light->intensity = d["intensity"]; + Ref<GLTFLight> light = GLTFLight::from_dictionary(lights[light_i]); + if (light.is_null()) { + return Error::ERR_PARSE_ERROR; } - if (d.has("range")) { - light->range = d["range"]; - } - if (type == "spot") { - const Dictionary &spot = d["spot"]; - light->inner_cone_angle = spot["innerConeAngle"]; - light->outer_cone_angle = spot["outerConeAngle"]; - ERR_CONTINUE_MSG(light->inner_cone_angle >= light->outer_cone_angle, "The inner angle must be smaller than the outer angle."); - } else if (type != "point" && type != "directional") { - ERR_CONTINUE_MSG(true, "Light type is unknown."); - } - state->lights.push_back(light); } @@ -4672,35 +4606,7 @@ Error GLTFDocument::_parse_cameras(Ref<GLTFState> state) { const Array cameras = state->json["cameras"]; for (GLTFCameraIndex i = 0; i < cameras.size(); i++) { - const Dictionary &d = cameras[i]; - - Ref<GLTFCamera> camera; - camera.instantiate(); - ERR_FAIL_COND_V(!d.has("type"), ERR_PARSE_ERROR); - const String &type = d["type"]; - if (type == "perspective") { - camera->set_perspective(true); - if (d.has("perspective")) { - const Dictionary &persp = d["perspective"]; - camera->set_fov(persp["yfov"]); - if (persp.has("zfar")) { - camera->set_depth_far(persp["zfar"]); - } - camera->set_depth_near(persp["znear"]); - } - } else if (type == "orthographic") { - camera->set_perspective(false); - if (d.has("orthographic")) { - const Dictionary &ortho = d["orthographic"]; - camera->set_size_mag(ortho["ymag"]); - camera->set_depth_far(ortho["zfar"]); - camera->set_depth_near(ortho["znear"]); - } - } else { - ERR_FAIL_V_MSG(ERR_PARSE_ERROR, "Camera3D should be in 'orthographic' or 'perspective'"); - } - - state->cameras.push_back(camera); + state->cameras.push_back(GLTFCamera::from_dictionary(cameras[i])); } print_verbose("glTF: Total cameras: " + itos(state->cameras.size())); @@ -5148,45 +5054,7 @@ Node3D *GLTFDocument::_generate_light(Ref<GLTFState> state, const GLTFNodeIndex print_verbose("glTF: Creating light for: " + gltf_node->get_name()); Ref<GLTFLight> l = state->lights[gltf_node->light]; - - float intensity = l->intensity; - if (intensity > 10) { - // GLTF spec has the default around 1, but Blender defaults lights to 100. - // The only sane way to handle this is to check where it came from and - // handle it accordingly. If it's over 10, it probably came from Blender. - intensity /= 100; - } - - if (l->light_type == "directional") { - DirectionalLight3D *light = memnew(DirectionalLight3D); - light->set_param(Light3D::PARAM_ENERGY, intensity); - light->set_color(l->color); - return light; - } - - const float range = CLAMP(l->range, 0, 4096); - if (l->light_type == "point") { - OmniLight3D *light = memnew(OmniLight3D); - light->set_param(OmniLight3D::PARAM_ENERGY, intensity); - light->set_param(OmniLight3D::PARAM_RANGE, range); - light->set_color(l->color); - return light; - } - if (l->light_type == "spot") { - SpotLight3D *light = memnew(SpotLight3D); - light->set_param(SpotLight3D::PARAM_ENERGY, intensity); - light->set_param(SpotLight3D::PARAM_RANGE, range); - light->set_param(SpotLight3D::PARAM_SPOT_ANGLE, Math::rad_to_deg(l->outer_cone_angle)); - light->set_color(l->color); - - // Line of best fit derived from guessing, see https://www.desmos.com/calculator/biiflubp8b - // The points in desmos are not exact, except for (1, infinity). - float angle_ratio = l->inner_cone_angle / l->outer_cone_angle; - float angle_attenuation = 0.2 / (1 - angle_ratio) - 0.1; - light->set_param(SpotLight3D::PARAM_SPOT_ATTENUATION, angle_attenuation); - return light; - } - return memnew(Node3D); + return l->to_node(); } Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> state, const GLTFNodeIndex node_index) { @@ -5194,32 +5062,16 @@ Camera3D *GLTFDocument::_generate_camera(Ref<GLTFState> state, const GLTFNodeInd ERR_FAIL_INDEX_V(gltf_node->camera, state->cameras.size(), nullptr); - Camera3D *camera = memnew(Camera3D); print_verbose("glTF: Creating camera for: " + gltf_node->get_name()); Ref<GLTFCamera> c = state->cameras[gltf_node->camera]; - camera->set_projection(c->get_perspective() ? Camera3D::PROJECTION_PERSPECTIVE : Camera3D::PROJECTION_ORTHOGONAL); - // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. - camera->set_fov(Math::rad_to_deg(c->get_fov())); - // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. - camera->set_size(c->get_size_mag() * 2.0f); - camera->set_near(c->get_depth_near()); - camera->set_far(c->get_depth_far()); - return camera; + return c->to_node(); } GLTFCameraIndex GLTFDocument::_convert_camera(Ref<GLTFState> state, Camera3D *p_camera) { print_verbose("glTF: Converting camera: " + p_camera->get_name()); - Ref<GLTFCamera> c; - c.instantiate(); - c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); - // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. - c->set_fov(Math::deg_to_rad(p_camera->get_fov())); - // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. - c->set_size_mag(p_camera->get_size() * 0.5f); - c->set_depth_far(p_camera->get_far()); - c->set_depth_near(p_camera->get_near()); + Ref<GLTFCamera> c = GLTFCamera::from_node(p_camera); GLTFCameraIndex camera_index = state->cameras.size(); state->cameras.push_back(c); return camera_index; @@ -5228,31 +5080,7 @@ GLTFCameraIndex GLTFDocument::_convert_camera(Ref<GLTFState> state, Camera3D *p_ GLTFLightIndex GLTFDocument::_convert_light(Ref<GLTFState> state, Light3D *p_light) { print_verbose("glTF: Converting light: " + p_light->get_name()); - Ref<GLTFLight> l; - l.instantiate(); - l->color = p_light->get_color(); - if (cast_to<DirectionalLight3D>(p_light)) { - l->light_type = "directional"; - DirectionalLight3D *light = cast_to<DirectionalLight3D>(p_light); - l->intensity = light->get_param(DirectionalLight3D::PARAM_ENERGY); - l->range = FLT_MAX; // Range for directional lights is infinite in Godot. - } else if (cast_to<OmniLight3D>(p_light)) { - l->light_type = "point"; - OmniLight3D *light = cast_to<OmniLight3D>(p_light); - l->range = light->get_param(OmniLight3D::PARAM_RANGE); - l->intensity = light->get_param(OmniLight3D::PARAM_ENERGY); - } else if (cast_to<SpotLight3D>(p_light)) { - l->light_type = "spot"; - SpotLight3D *light = cast_to<SpotLight3D>(p_light); - l->range = light->get_param(SpotLight3D::PARAM_RANGE); - l->intensity = light->get_param(SpotLight3D::PARAM_ENERGY); - l->outer_cone_angle = Math::deg_to_rad(light->get_param(SpotLight3D::PARAM_SPOT_ANGLE)); - - // This equation is the inverse of the import equation (which has a desmos link). - float angle_ratio = 1 - (0.2 / (0.1 + light->get_param(SpotLight3D::PARAM_SPOT_ATTENUATION))); - angle_ratio = MAX(0, angle_ratio); - l->inner_cone_angle = l->outer_cone_angle * angle_ratio; - } + Ref<GLTFLight> l = GLTFLight::from_node(p_light); GLTFLightIndex light_index = state->lights.size(); state->lights.push_back(l); diff --git a/modules/gltf/structures/gltf_camera.cpp b/modules/gltf/structures/gltf_camera.cpp index c492913ea7..5069f39c4b 100644 --- a/modules/gltf/structures/gltf_camera.cpp +++ b/modules/gltf/structures/gltf_camera.cpp @@ -31,6 +31,12 @@ #include "gltf_camera.h" void GLTFCamera::_bind_methods() { + ClassDB::bind_static_method("GLTFCamera", D_METHOD("from_node", "camera_node"), &GLTFCamera::from_node); + ClassDB::bind_method(D_METHOD("to_node"), &GLTFCamera::to_node); + + ClassDB::bind_static_method("GLTFCamera", D_METHOD("from_dictionary", "dictionary"), &GLTFCamera::from_dictionary); + ClassDB::bind_method(D_METHOD("to_dictionary"), &GLTFCamera::to_dictionary); + ClassDB::bind_method(D_METHOD("get_perspective"), &GLTFCamera::get_perspective); ClassDB::bind_method(D_METHOD("set_perspective", "perspective"), &GLTFCamera::set_perspective); ClassDB::bind_method(D_METHOD("get_fov"), &GLTFCamera::get_fov); @@ -48,3 +54,78 @@ void GLTFCamera::_bind_methods() { ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_far"), "set_depth_far", "get_depth_far"); ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "depth_near"), "set_depth_near", "get_depth_near"); } + +Ref<GLTFCamera> GLTFCamera::from_node(const Camera3D *p_camera) { + Ref<GLTFCamera> c; + c.instantiate(); + c->set_perspective(p_camera->get_projection() == Camera3D::ProjectionType::PROJECTION_PERSPECTIVE); + // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. + c->set_fov(Math::deg_to_rad(p_camera->get_fov())); + // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. + c->set_size_mag(p_camera->get_size() * 0.5f); + c->set_depth_far(p_camera->get_far()); + c->set_depth_near(p_camera->get_near()); + return c; +} + +Camera3D *GLTFCamera::to_node() const { + Camera3D *camera = memnew(Camera3D); + camera->set_projection(perspective ? Camera3D::PROJECTION_PERSPECTIVE : Camera3D::PROJECTION_ORTHOGONAL); + // GLTF spec (yfov) is in radians, Godot's camera (fov) is in degrees. + camera->set_fov(Math::rad_to_deg(fov)); + // GLTF spec (xmag and ymag) is a radius in meters, Godot's camera (size) is a diameter in meters. + camera->set_size(size_mag * 2.0f); + camera->set_near(depth_near); + camera->set_far(depth_far); + return camera; +} + +Ref<GLTFCamera> GLTFCamera::from_dictionary(const Dictionary p_dictionary) { + ERR_FAIL_COND_V_MSG(!p_dictionary.has("type"), Ref<GLTFCamera>(), "Failed to parse GLTF camera, missing required field 'type'."); + Ref<GLTFCamera> camera; + camera.instantiate(); + const String &type = p_dictionary["type"]; + if (type == "perspective") { + camera->set_perspective(true); + if (p_dictionary.has("perspective")) { + const Dictionary &persp = p_dictionary["perspective"]; + camera->set_fov(persp["yfov"]); + if (persp.has("zfar")) { + camera->set_depth_far(persp["zfar"]); + } + camera->set_depth_near(persp["znear"]); + } + } else if (type == "orthographic") { + camera->set_perspective(false); + if (p_dictionary.has("orthographic")) { + const Dictionary &ortho = p_dictionary["orthographic"]; + camera->set_size_mag(ortho["ymag"]); + camera->set_depth_far(ortho["zfar"]); + camera->set_depth_near(ortho["znear"]); + } + } else { + ERR_PRINT("Error parsing GLTF camera: Camera type '" + type + "' is unknown, should be perspective or orthographic."); + } + return camera; +} + +Dictionary GLTFCamera::to_dictionary() const { + Dictionary d; + if (perspective) { + Dictionary persp; + persp["yfov"] = fov; + persp["zfar"] = depth_far; + persp["znear"] = depth_near; + d["perspective"] = persp; + d["type"] = "perspective"; + } else { + Dictionary ortho; + ortho["ymag"] = size_mag; + ortho["xmag"] = size_mag; + ortho["zfar"] = depth_far; + ortho["znear"] = depth_near; + d["orthographic"] = ortho; + d["type"] = "orthographic"; + } + return d; +} diff --git a/modules/gltf/structures/gltf_camera.h b/modules/gltf/structures/gltf_camera.h index 8e528c063f..50ae10e17a 100644 --- a/modules/gltf/structures/gltf_camera.h +++ b/modules/gltf/structures/gltf_camera.h @@ -63,6 +63,12 @@ public: void set_depth_far(real_t p_val) { depth_far = p_val; } real_t get_depth_near() const { return depth_near; } void set_depth_near(real_t p_val) { depth_near = p_val; } + + static Ref<GLTFCamera> from_node(const Camera3D *p_light); + Camera3D *to_node() const; + + static Ref<GLTFCamera> from_dictionary(const Dictionary p_dictionary); + Dictionary to_dictionary() const; }; #endif // GLTF_CAMERA_H diff --git a/modules/mono/csharp_script.cpp b/modules/mono/csharp_script.cpp index 8b135051c5..2e59987fe6 100644 --- a/modules/mono/csharp_script.cpp +++ b/modules/mono/csharp_script.cpp @@ -694,7 +694,7 @@ bool CSharpLanguage::is_assembly_reloading_needed() { } assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() - .plus_file(assembly_name + ".dll"); + .path_join(assembly_name + ".dll"); assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path); if (!FileAccess::exists(assembly_path)) { @@ -1085,7 +1085,7 @@ void CSharpLanguage::_editor_init_callback() { const void **interop_funcs = godotsharp::get_editor_interop_funcs(interop_funcs_size); Object *editor_plugin_obj = GDMono::get_singleton()->get_plugin_callbacks().LoadToolsAssemblyCallback( - GodotSharpDirs::get_data_editor_tools_dir().plus_file("GodotTools.dll").utf16(), + GodotSharpDirs::get_data_editor_tools_dir().path_join("GodotTools.dll").utf16(), interop_funcs, interop_funcs_size); CRASH_COND(editor_plugin_obj == nullptr); diff --git a/modules/mono/editor/bindings_generator.cpp b/modules/mono/editor/bindings_generator.cpp index d70a1e6c88..0e496454a1 100644 --- a/modules/mono/editor/bindings_generator.cpp +++ b/modules/mono/editor/bindings_generator.cpp @@ -1315,7 +1315,7 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { // Generate GodotSharp source files - String core_proj_dir = output_dir.plus_file(CORE_API_ASSEMBLY_NAME); + String core_proj_dir = output_dir.path_join(CORE_API_ASSEMBLY_NAME); proj_err = generate_cs_core_project(core_proj_dir); if (proj_err != OK) { @@ -1325,7 +1325,7 @@ Error BindingsGenerator::generate_cs_api(const String &p_output_dir) { // Generate GodotSharpEditor source files - String editor_proj_dir = output_dir.plus_file(EDITOR_API_ASSEMBLY_NAME); + String editor_proj_dir = output_dir.path_join(EDITOR_API_ASSEMBLY_NAME); proj_err = generate_cs_editor_project(editor_proj_dir); if (proj_err != OK) { @@ -3880,7 +3880,7 @@ static void handle_cmdline_options(String glue_dir_path) { CRASH_COND(glue_dir_path.is_empty()); - if (bindings_generator.generate_cs_api(glue_dir_path.plus_file(API_SOLUTION_NAME)) != OK) { + if (bindings_generator.generate_cs_api(glue_dir_path.path_join(API_SOLUTION_NAME)) != OK) { ERR_PRINT(generate_all_glue_option + ": Failed to generate the C# API."); } } diff --git a/modules/mono/editor/code_completion.cpp b/modules/mono/editor/code_completion.cpp index 36c46627e2..40296eef10 100644 --- a/modules/mono/editor/code_completion.cpp +++ b/modules/mono/editor/code_completion.cpp @@ -163,9 +163,9 @@ PackedStringArray get_code_completion(CompletionKind p_kind, const String &p_scr } if (dir_access->dir_exists(filename)) { - directories.push_back(dir_access->get_current_dir().plus_file(filename)); + directories.push_back(dir_access->get_current_dir().path_join(filename)); } else if (filename.ends_with(".tscn") || filename.ends_with(".scn")) { - suggestions.push_back(quoted(dir_access->get_current_dir().plus_file(filename))); + suggestions.push_back(quoted(dir_access->get_current_dir().path_join(filename))); } filename = dir_access->get_next(); diff --git a/modules/mono/editor/editor_internal_calls.cpp b/modules/mono/editor/editor_internal_calls.cpp index 1ef78c3ac2..6f42ad6916 100644 --- a/modules/mono/editor/editor_internal_calls.cpp +++ b/modules/mono/editor/editor_internal_calls.cpp @@ -98,7 +98,7 @@ bool godot_icall_EditorProgress_Step(const godot_string *p_task, const godot_str } void godot_icall_Internal_FullExportTemplatesDir(godot_string *r_dest) { - String full_templates_dir = EditorPaths::get_singleton()->get_export_templates_dir().plus_file(VERSION_FULL_CONFIG); + String full_templates_dir = EditorPaths::get_singleton()->get_export_templates_dir().path_join(VERSION_FULL_CONFIG); memnew_placement(r_dest, String(full_templates_dir)); } diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs index 94dda5a74b..e84bba1179 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.cs @@ -436,6 +436,15 @@ namespace Godot.NativeInterop public static partial void godotsharp_string_simplify_path(in godot_string p_self, out godot_string r_simplified_path); + public static partial void godotsharp_string_to_camel_case(in godot_string p_self, + out godot_string r_camel_case); + + public static partial void godotsharp_string_to_pascal_case(in godot_string p_self, + out godot_string r_pascal_case); + + public static partial void godotsharp_string_to_snake_case(in godot_string p_self, + out godot_string r_snake_case); + // NodePath public static partial void godotsharp_node_path_get_as_property_path(in godot_node_path p_self, diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs index 2ea3c18d26..26fffc079c 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/NativeInterop/NativeFuncs.extended.cs @@ -11,31 +11,31 @@ namespace Godot.NativeInterop case Variant.Type.Nil: return default; case Variant.Type.Bool: - return new godot_variant() { Bool = src.Bool }; + return new godot_variant() { Bool = src.Bool, Type = Variant.Type.Bool }; case Variant.Type.Int: - return new godot_variant() { Int = src.Int }; + return new godot_variant() { Int = src.Int, Type = Variant.Type.Int }; case Variant.Type.Float: - return new godot_variant() { Float = src.Float }; + return new godot_variant() { Float = src.Float, Type = Variant.Type.Float }; case Variant.Type.Vector2: - return new godot_variant() { Vector2 = src.Vector2 }; + return new godot_variant() { Vector2 = src.Vector2, Type = Variant.Type.Vector2 }; case Variant.Type.Vector2i: - return new godot_variant() { Vector2i = src.Vector2i }; + return new godot_variant() { Vector2i = src.Vector2i, Type = Variant.Type.Vector2i }; case Variant.Type.Rect2: - return new godot_variant() { Rect2 = src.Rect2 }; + return new godot_variant() { Rect2 = src.Rect2, Type = Variant.Type.Rect2 }; case Variant.Type.Rect2i: - return new godot_variant() { Rect2i = src.Rect2i }; + return new godot_variant() { Rect2i = src.Rect2i, Type = Variant.Type.Rect2i }; case Variant.Type.Vector3: - return new godot_variant() { Vector3 = src.Vector3 }; + return new godot_variant() { Vector3 = src.Vector3, Type = Variant.Type.Vector3 }; case Variant.Type.Vector3i: - return new godot_variant() { Vector3i = src.Vector3i }; + return new godot_variant() { Vector3i = src.Vector3i, Type = Variant.Type.Vector3i }; case Variant.Type.Plane: - return new godot_variant() { Plane = src.Plane }; + return new godot_variant() { Plane = src.Plane, Type = Variant.Type.Plane }; case Variant.Type.Quaternion: - return new godot_variant() { Quaternion = src.Quaternion }; + return new godot_variant() { Quaternion = src.Quaternion, Type = Variant.Type.Quaternion }; case Variant.Type.Color: - return new godot_variant() { Color = src.Color }; + return new godot_variant() { Color = src.Color, Type = Variant.Type.Color }; case Variant.Type.Rid: - return new godot_variant() { RID = src.RID }; + return new godot_variant() { RID = src.RID, Type = Variant.Type.Rid }; } godotsharp_variant_new_copy(out godot_variant ret, src); diff --git a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs index f0bc5949df..44f951e314 100644 --- a/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs +++ b/modules/mono/glue/GodotSharp/GodotSharp/Core/StringExtensions.cs @@ -287,6 +287,45 @@ namespace Godot return cap; } + /// <summary> + /// Returns the string converted to <c>camelCase</c>. + /// </summary> + /// <param name="instance">The string to convert.</param> + /// <returns>The converted string.</returns> + public static string ToCamelCase(this string instance) + { + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_to_camel_case(instanceStr, out godot_string camelCase); + using (camelCase) + return Marshaling.ConvertStringToManaged(camelCase); + } + + /// <summary> + /// Returns the string converted to <c>PascalCase</c>. + /// </summary> + /// <param name="instance">The string to convert.</param> + /// <returns>The converted string.</returns> + public static string ToPascalCase(this string instance) + { + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_to_pascal_case(instanceStr, out godot_string pascalCase); + using (pascalCase) + return Marshaling.ConvertStringToManaged(pascalCase); + } + + /// <summary> + /// Returns the string converted to <c>snake_case</c>. + /// </summary> + /// <param name="instance">The string to convert.</param> + /// <returns>The converted string.</returns> + public static string ToSnakeCase(this string instance) + { + using godot_string instanceStr = Marshaling.ConvertStringToNative(instance); + NativeFuncs.godotsharp_string_to_snake_case(instanceStr, out godot_string snakeCase); + using (snakeCase) + return Marshaling.ConvertStringToManaged(snakeCase); + } + private static string CamelcaseToUnderscore(this string instance, bool lowerCase) { string newString = string.Empty; diff --git a/modules/mono/glue/runtime_interop.cpp b/modules/mono/glue/runtime_interop.cpp index 0d68cb54b9..529bda4c38 100644 --- a/modules/mono/glue/runtime_interop.cpp +++ b/modules/mono/glue/runtime_interop.cpp @@ -1096,6 +1096,18 @@ void godotsharp_string_simplify_path(const String *p_self, String *r_simplified_ memnew_placement(r_simplified_path, String(p_self->simplify_path())); } +void godotsharp_string_to_camel_case(const String *p_self, String *r_camel_case) { + memnew_placement(r_camel_case, String(p_self->to_camel_case())); +} + +void godotsharp_string_to_pascal_case(const String *p_self, String *r_pascal_case) { + memnew_placement(r_pascal_case, String(p_self->to_pascal_case())); +} + +void godotsharp_string_to_snake_case(const String *p_self, String *r_snake_case) { + memnew_placement(r_snake_case, String(p_self->to_snake_case())); +} + void godotsharp_node_path_get_as_property_path(const NodePath *p_ptr, NodePath *r_dest) { memnew_placement(r_dest, NodePath(p_ptr->get_as_property_path())); } @@ -1471,6 +1483,9 @@ static const void *unmanaged_callbacks[]{ (void *)godotsharp_string_sha256_buffer, (void *)godotsharp_string_sha256_text, (void *)godotsharp_string_simplify_path, + (void *)godotsharp_string_to_camel_case, + (void *)godotsharp_string_to_pascal_case, + (void *)godotsharp_string_to_snake_case, (void *)godotsharp_node_path_get_as_property_path, (void *)godotsharp_node_path_get_concatenated_names, (void *)godotsharp_node_path_get_concatenated_subnames, diff --git a/modules/mono/godotsharp_dirs.cpp b/modules/mono/godotsharp_dirs.cpp index 12960fb938..c7e47d2718 100644 --- a/modules/mono/godotsharp_dirs.cpp +++ b/modules/mono/godotsharp_dirs.cpp @@ -64,7 +64,7 @@ String _get_expected_build_config() { String _get_mono_user_dir() { #ifdef TOOLS_ENABLED if (EditorPaths::get_singleton()) { - return EditorPaths::get_singleton()->get_data_dir().plus_file("mono"); + return EditorPaths::get_singleton()->get_data_dir().path_join("mono"); } else { String settings_path; @@ -72,23 +72,23 @@ String _get_mono_user_dir() { String exe_dir = OS::get_singleton()->get_executable_path().get_base_dir(); // On macOS, look outside .app bundle, since .app bundle is read-only. - if (OS::get_singleton()->has_feature("macos") && exe_dir.ends_with("MacOS") && exe_dir.plus_file("..").simplify_path().ends_with("Contents")) { - exe_dir = exe_dir.plus_file("../../..").simplify_path(); + if (OS::get_singleton()->has_feature("macos") && exe_dir.ends_with("MacOS") && exe_dir.path_join("..").simplify_path().ends_with("Contents")) { + exe_dir = exe_dir.path_join("../../..").simplify_path(); } Ref<DirAccess> d = DirAccess::create_for_path(exe_dir); if (d->file_exists("._sc_") || d->file_exists("_sc_")) { // contain yourself - settings_path = exe_dir.plus_file("editor_data"); + settings_path = exe_dir.path_join("editor_data"); } else { - settings_path = OS::get_singleton()->get_data_path().plus_file(OS::get_singleton()->get_godot_dir_name()); + settings_path = OS::get_singleton()->get_data_path().path_join(OS::get_singleton()->get_godot_dir_name()); } - return settings_path.plus_file("mono"); + return settings_path.path_join("mono"); } #else - return OS::get_singleton()->get_user_data_dir().plus_file("mono"); + return OS::get_singleton()->get_user_data_dir().path_join("mono"); #endif } @@ -126,27 +126,27 @@ public: private: _GodotSharpDirs() { - res_data_dir = ProjectSettings::get_singleton()->get_project_data_path().plus_file("mono"); - res_metadata_dir = res_data_dir.plus_file("metadata"); - res_config_dir = res_data_dir.plus_file("etc").plus_file("mono"); + res_data_dir = ProjectSettings::get_singleton()->get_project_data_path().path_join("mono"); + res_metadata_dir = res_data_dir.path_join("metadata"); + res_config_dir = res_data_dir.path_join("etc").path_join("mono"); // TODO use paths from csproj - res_temp_dir = res_data_dir.plus_file("temp"); - res_temp_assemblies_base_dir = res_temp_dir.plus_file("bin"); - res_temp_assemblies_dir = res_temp_assemblies_base_dir.plus_file(_get_expected_build_config()); + res_temp_dir = res_data_dir.path_join("temp"); + res_temp_assemblies_base_dir = res_temp_dir.path_join("bin"); + res_temp_assemblies_dir = res_temp_assemblies_base_dir.path_join(_get_expected_build_config()); - api_assemblies_base_dir = res_data_dir.plus_file("assemblies"); + api_assemblies_base_dir = res_data_dir.path_join("assemblies"); #ifdef WEB_ENABLED mono_user_dir = "user://"; #else mono_user_dir = _get_mono_user_dir(); #endif - mono_logs_dir = mono_user_dir.plus_file("mono_logs"); + mono_logs_dir = mono_user_dir.path_join("mono_logs"); #ifdef TOOLS_ENABLED - mono_solutions_dir = mono_user_dir.plus_file("solutions"); - build_logs_dir = mono_user_dir.plus_file("build_logs"); + mono_solutions_dir = mono_user_dir.path_join("solutions"); + build_logs_dir = mono_user_dir.path_join("build_logs"); String base_path = ProjectSettings::get_singleton()->globalize_path("res://"); #endif @@ -155,35 +155,35 @@ private: #ifdef TOOLS_ENABLED - String data_dir_root = exe_dir.plus_file("GodotSharp"); - data_editor_tools_dir = data_dir_root.plus_file("Tools"); - api_assemblies_base_dir = data_dir_root.plus_file("Api"); + String data_dir_root = exe_dir.path_join("GodotSharp"); + data_editor_tools_dir = data_dir_root.path_join("Tools"); + api_assemblies_base_dir = data_dir_root.path_join("Api"); - String data_mono_root_dir = data_dir_root.plus_file("Mono"); - data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); + String data_mono_root_dir = data_dir_root.path_join("Mono"); + data_mono_etc_dir = data_mono_root_dir.path_join("etc"); #ifdef ANDROID_ENABLED data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir(); #else - data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); + data_mono_lib_dir = data_mono_root_dir.path_join("lib"); #endif #ifdef WINDOWS_ENABLED - data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); + data_mono_bin_dir = data_mono_root_dir.path_join("bin"); #endif #ifdef MACOS_ENABLED if (!DirAccess::exists(data_editor_tools_dir)) { - data_editor_tools_dir = exe_dir.plus_file("../Resources/GodotSharp/Tools"); + data_editor_tools_dir = exe_dir.path_join("../Resources/GodotSharp/Tools"); } if (!DirAccess::exists(api_assemblies_base_dir)) { - api_assemblies_base_dir = exe_dir.plus_file("../Resources/GodotSharp/Api"); + api_assemblies_base_dir = exe_dir.path_join("../Resources/GodotSharp/Api"); } if (!DirAccess::exists(data_mono_root_dir)) { - data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc"); - data_mono_lib_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/lib"); + data_mono_etc_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/etc"); + data_mono_lib_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/lib"); } #endif @@ -191,40 +191,40 @@ private: String appname = ProjectSettings::get_singleton()->get("application/config/name"); String appname_safe = OS::get_singleton()->get_safe_dir_name(appname); - String data_dir_root = exe_dir.plus_file("data_" + appname_safe); + String data_dir_root = exe_dir.path_join("data_" + appname_safe); if (!DirAccess::exists(data_dir_root)) { - data_dir_root = exe_dir.plus_file("data_Godot"); + data_dir_root = exe_dir.path_join("data_Godot"); } - String data_mono_root_dir = data_dir_root.plus_file("Mono"); - data_mono_etc_dir = data_mono_root_dir.plus_file("etc"); + String data_mono_root_dir = data_dir_root.path_join("Mono"); + data_mono_etc_dir = data_mono_root_dir.path_join("etc"); #ifdef ANDROID_ENABLED data_mono_lib_dir = gdmono::android::support::get_app_native_lib_dir(); #else - data_mono_lib_dir = data_mono_root_dir.plus_file("lib"); - data_game_assemblies_dir = data_dir_root.plus_file("Assemblies"); + data_mono_lib_dir = data_mono_root_dir.path_join("lib"); + data_game_assemblies_dir = data_dir_root.path_join("Assemblies"); #endif #ifdef WINDOWS_ENABLED - data_mono_bin_dir = data_mono_root_dir.plus_file("bin"); + data_mono_bin_dir = data_mono_root_dir.path_join("bin"); #endif #ifdef MACOS_ENABLED if (!DirAccess::exists(data_mono_root_dir)) { - data_mono_etc_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/etc"); - data_mono_lib_dir = exe_dir.plus_file("../Resources/GodotSharp/Mono/lib"); + data_mono_etc_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/etc"); + data_mono_lib_dir = exe_dir.path_join("../Resources/GodotSharp/Mono/lib"); } if (!DirAccess::exists(data_game_assemblies_dir)) { - data_game_assemblies_dir = exe_dir.plus_file("../Resources/GodotSharp/Assemblies"); + data_game_assemblies_dir = exe_dir.path_join("../Resources/GodotSharp/Assemblies"); } #endif #endif #ifdef TOOLS_ENABLED - api_assemblies_dir = api_assemblies_base_dir.plus_file(GDMono::get_expected_api_build_config()); + api_assemblies_dir = api_assemblies_base_dir.path_join(GDMono::get_expected_api_build_config()); #else api_assemblies_dir = data_dir_root; #endif diff --git a/modules/mono/mono_gd/gd_mono.cpp b/modules/mono/mono_gd/gd_mono.cpp index eda4924f15..e698e92d7a 100644 --- a/modules/mono/mono_gd/gd_mono.cpp +++ b/modules/mono/mono_gd/gd_mono.cpp @@ -173,13 +173,13 @@ String find_hostfxr() { #if defined(WINDOWS_ENABLED) String probe_path = GodotSharpDirs::get_api_assemblies_dir() - .plus_file("hostfxr.dll"); + .path_join("hostfxr.dll"); #elif defined(MACOS_ENABLED) String probe_path = GodotSharpDirs::get_api_assemblies_dir() - .plus_file("libhostfxr.dylib"); + .path_join("libhostfxr.dylib"); #elif defined(UNIX_ENABLED) String probe_path = GodotSharpDirs::get_api_assemblies_dir() - .plus_file("libhostfxr.so"); + .path_join("libhostfxr.so"); #else #error "Platform not supported (yet?)" #endif @@ -305,10 +305,10 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime godot_plugins_initialize_fn godot_plugins_initialize = nullptr; HostFxrCharString godot_plugins_path = str_to_hostfxr( - GodotSharpDirs::get_api_assemblies_dir().plus_file("GodotPlugins.dll")); + GodotSharpDirs::get_api_assemblies_dir().path_join("GodotPlugins.dll")); HostFxrCharString config_path = str_to_hostfxr( - GodotSharpDirs::get_api_assemblies_dir().plus_file("GodotPlugins.runtimeconfig.json")); + GodotSharpDirs::get_api_assemblies_dir().path_join("GodotPlugins.runtimeconfig.json")); load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = initialize_hostfxr_for_config(get_data(config_path)); @@ -345,7 +345,7 @@ godot_plugins_initialize_fn initialize_hostfxr_and_godot_plugins(bool &r_runtime String assembly_name = get_assembly_name(); HostFxrCharString assembly_path = str_to_hostfxr(GodotSharpDirs::get_api_assemblies_dir() - .plus_file(assembly_name + ".dll")); + .path_join(assembly_name + ".dll")); load_assembly_and_get_function_pointer_fn load_assembly_and_get_function_pointer = initialize_hostfxr_self_contained(get_data(assembly_path)); @@ -370,11 +370,11 @@ godot_plugins_initialize_fn try_load_native_aot_library(void *&r_aot_dll_handle) String assembly_name = get_assembly_name(); #if defined(WINDOWS_ENABLED) - String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".dll"); + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dll"); #elif defined(MACOS_ENABLED) - String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".dylib"); + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".dylib"); #elif defined(UNIX_ENABLED) - String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().plus_file(assembly_name + ".so"); + String native_aot_so_path = GodotSharpDirs::get_api_assemblies_dir().path_join(assembly_name + ".so"); #else #error "Platform not supported (yet?)" #endif @@ -514,7 +514,7 @@ bool GDMono::_load_project_assembly() { } String assembly_path = GodotSharpDirs::get_res_temp_assemblies_dir() - .plus_file(assembly_name + ".dll"); + .path_join(assembly_name + ".dll"); assembly_path = ProjectSettings::get_singleton()->globalize_path(assembly_path); if (!FileAccess::exists(assembly_path)) { diff --git a/modules/mono/utils/path_utils.cpp b/modules/mono/utils/path_utils.cpp index 19ad59a1bc..269e41e2f4 100644 --- a/modules/mono/utils/path_utils.cpp +++ b/modules/mono/utils/path_utils.cpp @@ -205,7 +205,7 @@ String relative_to_impl(const String &p_path, const String &p_relative_to) { return p_path; } - return String("..").plus_file(relative_to_impl(p_path, base_dir)); + return String("..").path_join(relative_to_impl(p_path, base_dir)); } } |