diff options
Diffstat (limited to 'editor')
-rw-r--r-- | editor/editor_atlas_packer.cpp | 4 | ||||
-rw-r--r-- | editor/editor_inspector.cpp | 32 | ||||
-rw-r--r-- | editor/editor_inspector.h | 2 | ||||
-rw-r--r-- | editor/editor_node.cpp | 8 | ||||
-rw-r--r-- | editor/export/editor_export.cpp | 2 | ||||
-rw-r--r-- | editor/export/editor_export_platform.cpp | 543 | ||||
-rw-r--r-- | editor/export/editor_export_platform.h | 16 | ||||
-rw-r--r-- | editor/export/editor_export_plugin.cpp | 96 | ||||
-rw-r--r-- | editor/export/editor_export_plugin.h | 35 | ||||
-rw-r--r-- | editor/icons/NavigationLink2D.svg | 4 | ||||
-rw-r--r-- | editor/icons/NavigationLink3D.svg | 4 | ||||
-rw-r--r-- | editor/import/resource_importer_bitmask.cpp | 2 | ||||
-rw-r--r-- | editor/plugins/editor_preview_plugins.cpp | 2 | ||||
-rw-r--r-- | editor/plugins/gdextension_export_plugin.h | 1 | ||||
-rw-r--r-- | editor/plugins/navigation_link_2d_editor_plugin.cpp | 191 | ||||
-rw-r--r-- | editor/plugins/navigation_link_2d_editor_plugin.h | 83 | ||||
-rw-r--r-- | editor/plugins/node_3d_editor_gizmos.cpp | 170 | ||||
-rw-r--r-- | editor/plugins/node_3d_editor_gizmos.h | 17 | ||||
-rw-r--r-- | editor/plugins/node_3d_editor_plugin.cpp | 1 |
19 files changed, 1107 insertions, 106 deletions
diff --git a/editor/editor_atlas_packer.cpp b/editor/editor_atlas_packer.cpp index 9c6bcd769a..7f4bd8cc89 100644 --- a/editor/editor_atlas_packer.cpp +++ b/editor/editor_atlas_packer.cpp @@ -81,7 +81,7 @@ void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_h int l = k == 0 ? 2 : k - 1; Vector<Point2i> points = Geometry2D::bresenham_line(v[k], v[l]); for (Point2i point : points) { - src_bitmap->set_bit(point, true); + src_bitmap->set_bitv(point, true); } } } @@ -128,7 +128,7 @@ void EditorAtlasPacker::chart_pack(Vector<Chart> &charts, int &r_width, int &r_h continue; } - if (src_bitmap->get_bit(Vector2(px, py))) { + if (src_bitmap->get_bit(px, py)) { found_pixel = true; } } diff --git a/editor/editor_inspector.cpp b/editor/editor_inspector.cpp index 6aa0bd3f99..bdf1c314f8 100644 --- a/editor/editor_inspector.cpp +++ b/editor/editor_inspector.cpp @@ -3949,16 +3949,22 @@ void EditorInspector::_add_meta_confirm() { undo_redo->commit_action(); } -void EditorInspector::_check_meta_name(String name) { +void EditorInspector::_check_meta_name(const String &p_name) { String error; - if (name == "") { - error = TTR("Metadata can't be empty."); - } else if (!name.is_valid_identifier()) { - error = TTR("Invalid metadata identifier."); - } else if (object->has_meta(name)) { - error = TTR("Metadata already exists."); - } else if (name[0] == '_') { + if (p_name == "") { + error = TTR("Metadata name can't be empty."); + } else if (!p_name.is_valid_identifier()) { + error = TTR("Metadata name must be a valid identifier."); + } else if (object->has_meta(p_name)) { + Node *node = Object::cast_to<Node>(object); + if (node) { + error = vformat(TTR("Metadata with name \"%s\" already exists on \"%s\"."), p_name, node->get_name()); + } else { + // This should normally never be reached, but the error is set just in case. + error = vformat(TTR("Metadata with name \"%s\" already exists."), p_name, node->get_name()); + } + } else if (p_name[0] == '_') { error = TTR("Names starting with _ are reserved for editor-only metadata."); } @@ -3976,7 +3982,15 @@ void EditorInspector::_check_meta_name(String name) { void EditorInspector::_show_add_meta_dialog() { if (!add_meta_dialog) { add_meta_dialog = memnew(ConfirmationDialog); - add_meta_dialog->set_title(TTR("Add Metadata Property")); + + Node *node = Object::cast_to<Node>(object); + if (node) { + add_meta_dialog->set_title(vformat(TTR("Add Metadata Property for \"%s\""), node->get_name())); + } else { + // This should normally never be reached, but the title is set just in case. + add_meta_dialog->set_title(vformat(TTR("Add Metadata Property"), node->get_name())); + } + VBoxContainer *vbc = memnew(VBoxContainer); add_meta_dialog->add_child(vbc); HBoxContainer *hbc = memnew(HBoxContainer); diff --git a/editor/editor_inspector.h b/editor/editor_inspector.h index 474078853a..d634eae23f 100644 --- a/editor/editor_inspector.h +++ b/editor/editor_inspector.h @@ -538,7 +538,7 @@ class EditorInspector : public ScrollContainer { void _add_meta_confirm(); void _show_add_meta_dialog(); - void _check_meta_name(String name); + void _check_meta_name(const String &p_name); protected: static void _bind_methods(); diff --git a/editor/editor_node.cpp b/editor/editor_node.cpp index 689a13ab5c..c2820389c6 100644 --- a/editor/editor_node.cpp +++ b/editor/editor_node.cpp @@ -170,6 +170,7 @@ #include "editor/plugins/mesh_instance_3d_editor_plugin.h" #include "editor/plugins/mesh_library_editor_plugin.h" #include "editor/plugins/multimesh_editor_plugin.h" +#include "editor/plugins/navigation_link_2d_editor_plugin.h" #include "editor/plugins/navigation_polygon_editor_plugin.h" #include "editor/plugins/node_3d_editor_plugin.h" #include "editor/plugins/occluder_instance_3d_editor_plugin.h" @@ -4110,6 +4111,7 @@ void EditorNode::register_editor_types() { GDREGISTER_CLASS(EditorSyntaxHighlighter); GDREGISTER_ABSTRACT_CLASS(EditorInterface); GDREGISTER_CLASS(EditorExportPlugin); + GDREGISTER_ABSTRACT_CLASS(EditorExportPlatform); GDREGISTER_CLASS(EditorResourceConversionPlugin); GDREGISTER_CLASS(EditorSceneFormatImporter); GDREGISTER_CLASS(EditorScenePostImportPlugin); @@ -7348,6 +7350,7 @@ EditorNode::EditorNode() { add_editor_plugin(memnew(GPUParticles2DEditorPlugin)); add_editor_plugin(memnew(LightOccluder2DEditorPlugin)); add_editor_plugin(memnew(Line2DEditorPlugin)); + add_editor_plugin(memnew(NavigationLink2DEditorPlugin)); add_editor_plugin(memnew(NavigationPolygonEditorPlugin)); add_editor_plugin(memnew(Path2DEditorPlugin)); add_editor_plugin(memnew(Polygon2DEditorPlugin)); @@ -7421,11 +7424,6 @@ EditorNode::EditorNode() { editor_plugins_force_over = memnew(EditorPluginList); editor_plugins_force_input_forwarding = memnew(EditorPluginList); - Ref<EditorExportTextSceneToBinaryPlugin> export_text_to_binary_plugin; - export_text_to_binary_plugin.instantiate(); - - EditorExport::get_singleton()->add_export_plugin(export_text_to_binary_plugin); - Ref<GDExtensionExportPlugin> gdextension_export_plugin; gdextension_export_plugin.instantiate(); diff --git a/editor/export/editor_export.cpp b/editor/export/editor_export.cpp index d291040bd2..29b6a5e546 100644 --- a/editor/export/editor_export.cpp +++ b/editor/export/editor_export.cpp @@ -351,6 +351,8 @@ EditorExport::EditorExport() { singleton = this; set_process(true); + + GLOBAL_DEF("editor/export/convert_text_resources_to_binary", true); } EditorExport::~EditorExport() { diff --git a/editor/export/editor_export_platform.cpp b/editor/export/editor_export_platform.cpp index e07b5e4cdb..525a962222 100644 --- a/editor/export/editor_export_platform.cpp +++ b/editor/export/editor_export_platform.cpp @@ -44,6 +44,7 @@ #include "editor/editor_settings.h" #include "editor/plugins/script_editor_plugin.h" #include "editor_export_plugin.h" +#include "scene/resources/packed_scene.h" static int _get_pad(int p_alignment, int p_n) { int rest = p_n % p_alignment; @@ -488,6 +489,295 @@ EditorExportPlatform::ExportNotifier::~ExportNotifier() { } } +bool EditorExportPlatform::_export_customize_dictionary(Dictionary &dict, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { + bool changed = false; + + List<Variant> keys; + dict.get_key_list(&keys); + for (const Variant &K : keys) { + Variant v = dict[K]; + switch (v.get_type()) { + case Variant::OBJECT: { + Ref<Resource> res = v; + if (res.is_valid()) { + for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) { + Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, ""); + if (new_res.is_valid()) { + changed = true; + if (new_res != res) { + dict[K] = new_res; + res = new_res; + } + break; + } + } + + // If it was not replaced, go through and see if there is something to replace. + if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) { + changed = true; + } + } + + } break; + case Variant::DICTIONARY: { + Dictionary d = v; + if (_export_customize_dictionary(d, customize_resources_plugins)) { + changed = true; + } + } break; + case Variant::ARRAY: { + Array a = v; + if (_export_customize_array(a, customize_resources_plugins)) { + changed = true; + } + } break; + default: { + } + } + } + return changed; +} + +bool EditorExportPlatform::_export_customize_array(Array &arr, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { + bool changed = false; + + for (int i = 0; i < arr.size(); i++) { + Variant v = arr.get(i); + switch (v.get_type()) { + case Variant::OBJECT: { + Ref<Resource> res = v; + if (res.is_valid()) { + for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) { + Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, ""); + if (new_res.is_valid()) { + changed = true; + if (new_res != res) { + arr.set(i, new_res); + res = new_res; + } + break; + } + } + + // If it was not replaced, go through and see if there is something to replace. + if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) { + changed = true; + } + } + } break; + case Variant::DICTIONARY: { + Dictionary d = v; + if (_export_customize_dictionary(d, customize_resources_plugins)) { + changed = true; + } + } break; + case Variant::ARRAY: { + Array a = v; + if (_export_customize_array(a, customize_resources_plugins)) { + changed = true; + } + } break; + default: { + } + } + } + return changed; +} + +bool EditorExportPlatform::_export_customize_object(Object *p_object, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { + bool changed = false; + + List<PropertyInfo> props; + p_object->get_property_list(&props); + for (const PropertyInfo &E : props) { + switch (E.type) { + case Variant::OBJECT: { + Ref<Resource> res = p_object->get(E.name); + if (res.is_valid()) { + for (uint32_t j = 0; j < customize_resources_plugins.size(); j++) { + Ref<Resource> new_res = customize_resources_plugins[j]->_customize_resource(res, ""); + if (new_res.is_valid()) { + changed = true; + if (new_res != res) { + p_object->set(E.name, new_res); + res = new_res; + } + break; + } + } + + // If it was not replaced, go through and see if there is something to replace. + if (res.is_valid() && !res->get_path().is_resource_file() && _export_customize_object(res.ptr(), customize_resources_plugins), true) { + changed = true; + } + } + + } break; + case Variant::DICTIONARY: { + Dictionary d = p_object->get(E.name); + if (_export_customize_dictionary(d, customize_resources_plugins)) { + // May have been generated, so set back just in case + p_object->set(E.name, d); + changed = true; + } + } break; + case Variant::ARRAY: { + Array a = p_object->get(E.name); + if (_export_customize_array(a, customize_resources_plugins)) { + // May have been generated, so set back just in case + p_object->set(E.name, a); + changed = true; + } + } break; + default: { + } + } + } + return changed; +} + +bool EditorExportPlatform::_export_customize_scene_resources(Node *p_root, Node *p_node, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins) { + bool changed = false; + + if (p_node == p_root || p_node->get_owner() == p_root) { + if (_export_customize_object(p_node, customize_resources_plugins)) { + changed = true; + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + if (_export_customize_scene_resources(p_root, p_node->get_child(i), customize_resources_plugins)) { + changed = true; + } + } + + return changed; +} + +String EditorExportPlatform::_export_customize(const String &p_path, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins, LocalVector<Ref<EditorExportPlugin>> &customize_scenes_plugins, HashMap<String, FileExportCache> &export_cache, const String &export_base_path, bool p_force_save) { + if (!p_force_save && customize_resources_plugins.is_empty() && customize_scenes_plugins.is_empty()) { + return p_path; // do none + } + + // Check if a cache exists + if (export_cache.has(p_path)) { + FileExportCache &fec = export_cache[p_path]; + + if (fec.saved_path.is_empty() || FileAccess::exists(fec.saved_path)) { + // Destination file exists (was not erased) or not needed + + uint64_t mod_time = FileAccess::get_modified_time(p_path); + if (fec.source_modified_time == mod_time) { + // Cached (modified time matches). + fec.used = true; + return fec.saved_path.is_empty() ? p_path : fec.saved_path; + } + + String md5 = FileAccess::get_md5(p_path); + if (FileAccess::exists(p_path + ".import")) { + // Also consider the import file in the string + md5 += FileAccess::get_md5(p_path + ".import"); + } + if (fec.source_md5 == md5) { + // Cached (md5 matches). + fec.source_modified_time = mod_time; + fec.used = true; + return fec.saved_path.is_empty() ? p_path : fec.saved_path; + } + } + } + + FileExportCache fec; + fec.used = true; + fec.source_modified_time = FileAccess::get_modified_time(p_path); + + String md5 = FileAccess::get_md5(p_path); + if (FileAccess::exists(p_path + ".import")) { + // Also consider the import file in the string + md5 += FileAccess::get_md5(p_path + ".import"); + } + + fec.source_md5 = md5; + + // Check if it should convert + + String type = ResourceLoader::get_resource_type(p_path); + + bool modified = false; + + String save_path; + + if (type == "PackedScene") { // Its a scene. + Ref<PackedScene> ps = ResourceLoader::load(p_path, "PackedScene", ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND_V(ps.is_null(), p_path); + Node *node = ps->instantiate(); + ERR_FAIL_COND_V(node == nullptr, p_path); + if (customize_scenes_plugins.size()) { + for (uint32_t i = 0; i < customize_scenes_plugins.size(); i++) { + Node *customized = customize_scenes_plugins[i]->_customize_scene(node, p_path); + if (customized != nullptr) { + node = customized; + modified = true; + } + } + } + if (customize_resources_plugins.size()) { + if (_export_customize_scene_resources(node, node, customize_resources_plugins)) { + modified = true; + } + } + + if (modified || p_force_save) { + // If modified, save it again. This is also used for TSCN -> SCN conversion on export. + + String base_file = p_path.get_file().get_basename() + ".scn"; // use SCN for saving (binary) and repack (If conversting, TSCN PackedScene representation is inefficient, so repacking is also desired). + save_path = export_base_path.path_join("export-" + p_path.md5_text() + "-" + base_file); + + Ref<PackedScene> s; + s.instantiate(); + s->pack(node); + Error err = ResourceSaver::save(s, save_path); + ERR_FAIL_COND_V_MSG(err != OK, p_path, "Unable to save export scene file to: " + save_path); + } + } else { + Ref<Resource> res = ResourceLoader::load(p_path, "", ResourceFormatLoader::CACHE_MODE_IGNORE); + ERR_FAIL_COND_V(res.is_null(), p_path); + + if (customize_resources_plugins.size()) { + for (uint32_t i = 0; i < customize_resources_plugins.size(); i++) { + Ref<Resource> new_res = customize_resources_plugins[i]->_customize_resource(res, p_path); + if (new_res.is_valid()) { + modified = true; + if (new_res != res) { + res = new_res; + } + break; + } + } + + if (_export_customize_object(res.ptr(), customize_resources_plugins)) { + modified = true; + } + } + + if (modified || p_force_save) { + // If modified, save it again. This is also used for TRES -> RES conversion on export. + + String base_file = p_path.get_file().get_basename() + ".res"; // use RES for saving (binary) + save_path = export_base_path.path_join("export-" + p_path.md5_text() + "-" + base_file); + + Error err = ResourceSaver::save(res, save_path); + ERR_FAIL_COND_V_MSG(err != OK, p_path, "Unable to save export resource file to: " + save_path); + } + } + + fec.saved_path = save_path; + + export_cache[p_path] = fec; + + return save_path.is_empty() ? p_path : save_path; +} + Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> &p_preset, bool p_debug, EditorExportSaveFunction p_func, void *p_udata, EditorExportSaveSharedObject p_so_func) { //figure out paths of files that will be exported HashSet<String> paths; @@ -601,6 +891,15 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & Error err = OK; Vector<Ref<EditorExportPlugin>> export_plugins = EditorExport::get_singleton()->get_export_plugins(); + struct SortByName { + bool operator()(const Ref<EditorExportPlugin> &left, const Ref<EditorExportPlugin> &right) const { + return left->_get_name() < right->_get_name(); + } + }; + + // Always sort by name, to so if for some reason theya are re-arranged, it still works. + export_plugins.sort_custom<SortByName>(); + for (int i = 0; i < export_plugins.size(); i++) { export_plugins.write[i]->set_export_preset(p_preset); @@ -623,6 +922,65 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } HashSet<String> features = get_features(p_preset, p_debug); + PackedStringArray features_psa; + for (const String &feature : features) { + features_psa.push_back(feature); + } + + // Check if custom processing is needed + uint32_t custom_resources_hash = HASH_MURMUR3_SEED; + uint32_t custom_scene_hash = HASH_MURMUR3_SEED; + + LocalVector<Ref<EditorExportPlugin>> customize_resources_plugins; + LocalVector<Ref<EditorExportPlugin>> customize_scenes_plugins; + + for (int i = 0; i < export_plugins.size(); i++) { + if (export_plugins[i]->_begin_customize_resources(Ref<EditorExportPlatform>(this), features_psa)) { + customize_resources_plugins.push_back(export_plugins[i]); + + custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash); + uint64_t hash = export_plugins[i]->_get_customization_configuration_hash(); + custom_resources_hash = hash_murmur3_one_64(hash, custom_resources_hash); + } + if (export_plugins[i]->_begin_customize_scenes(Ref<EditorExportPlatform>(this), features_psa)) { + customize_scenes_plugins.push_back(export_plugins[i]); + + custom_resources_hash = hash_murmur3_one_64(export_plugins[i]->_get_name().hash64(), custom_resources_hash); + uint64_t hash = export_plugins[i]->_get_customization_configuration_hash(); + custom_scene_hash = hash_murmur3_one_64(hash, custom_scene_hash); + } + } + + HashMap<String, FileExportCache> export_cache; + String export_base_path = ProjectSettings::get_singleton()->get_project_data_path().path_join("exported/") + itos(custom_resources_hash); + + bool convert_text_to_binary = GLOBAL_GET("editor/export/convert_text_resources_to_binary"); + + if (convert_text_to_binary || customize_resources_plugins.size() || customize_scenes_plugins.size()) { + // See if we have something to open + Ref<FileAccess> f = FileAccess::open(export_base_path.path_join("file_cache"), FileAccess::READ); + if (f.is_valid()) { + String l = f->get_line(); + while (l != String()) { + Vector<String> fields = l.split("::"); + if (fields.size() == 4) { + FileExportCache fec; + String path = fields[0]; + fec.source_md5 = fields[1].strip_edges(); + fec.source_modified_time = fields[2].strip_edges().to_int(); + fec.saved_path = fields[3]; + fec.used = false; // Assume unused until used. + export_cache[path] = fec; + } + l = f->get_line(); + } + } else { + // create the path + Ref<DirAccess> d = DirAccess::create(DirAccess::ACCESS_RESOURCES); + d->change_dir(ProjectSettings::get_singleton()->get_project_data_path()); + d->make_dir_recursive("exported/" + itos(custom_resources_hash)); + } + } //store everything in the export medium int idx = 0; @@ -633,85 +991,133 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & String type = ResourceLoader::get_resource_type(path); if (FileAccess::exists(path + ".import")) { - //file is imported, replace by what it imports - Ref<ConfigFile> config; - config.instantiate(); - err = config->load(path + ".import"); - if (err != OK) { - ERR_PRINT("Could not parse: '" + path + "', not exported."); - continue; - } + // Before doing this, try to see if it can be customized - String importer_type = config->get_value("remap", "importer"); + String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, false); - if (importer_type == "keep") { - //just keep file as-is - Vector<uint8_t> array = FileAccess::get_file_as_array(path); - err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); + if (export_path != path) { + // It was actually customized.. + // Since the original file is likely not recognized, just use the import system + Ref<ConfigFile> config; + config.instantiate(); + err = config->load(path + ".import"); + if (err != OK) { + ERR_PRINT("Could not parse: '" + path + "', not exported."); + continue; + } + config->set_value("remap", "type", ResourceLoader::get_resource_type(export_path)); + + // Erase all PAths + List<String> keys; + config->get_section_keys("remap", &keys); + for (const String &K : keys) { + if (E.begins_with("path")) { + config->erase_section_key("remap", K); + } + } + // Set actual converted path. + config->set_value("remap", "path", export_path); + + // erase useless sections + config->erase_section("deps"); + config->erase_section("params"); + + String import_text = config->encode_to_text(); + CharString cs = import_text.utf8(); + Vector<uint8_t> sarr; + sarr.resize(cs.size()); + memcpy(sarr.ptrw(), cs.ptr(), sarr.size()); + + err = p_func(p_udata, path + ".import", sarr, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; } + // Now actual remapped file: + sarr = FileAccess::get_file_as_array(export_path); + err = p_func(p_udata, export_path, sarr, idx, total, enc_in_filters, enc_ex_filters, key); + if (err != OK) { + return err; + } + } else { + // file is imported and not customized, replace by what it imports + Ref<ConfigFile> config; + config.instantiate(); + err = config->load(path + ".import"); + if (err != OK) { + ERR_PRINT("Could not parse: '" + path + "', not exported."); + continue; + } - continue; - } + String importer_type = config->get_value("remap", "importer"); - List<String> remaps; - config->get_section_keys("remap", &remaps); + if (importer_type == "keep") { + //just keep file as-is + Vector<uint8_t> array = FileAccess::get_file_as_array(path); + err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); - HashSet<String> remap_features; + if (err != OK) { + return err; + } - for (const String &F : remaps) { - String remap = F; - String feature = remap.get_slice(".", 1); - if (features.has(feature)) { - remap_features.insert(feature); + continue; } - } - if (remap_features.size() > 1) { - this->resolve_platform_feature_priorities(p_preset, remap_features); - } + List<String> remaps; + config->get_section_keys("remap", &remaps); - err = OK; + HashSet<String> remap_features; - for (const String &F : remaps) { - String remap = F; - if (remap == "path") { - String remapped_path = config->get_value("remap", remap); - Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path); - err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); - } else if (remap.begins_with("path.")) { + for (const String &F : remaps) { + String remap = F; String feature = remap.get_slice(".", 1); + if (features.has(feature)) { + remap_features.insert(feature); + } + } - if (remap_features.has(feature)) { + if (remap_features.size() > 1) { + this->resolve_platform_feature_priorities(p_preset, remap_features); + } + + err = OK; + + for (const String &F : remaps) { + String remap = F; + if (remap == "path") { String remapped_path = config->get_value("remap", remap); Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path); err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + } else if (remap.begins_with("path.")) { + String feature = remap.get_slice(".", 1); + + if (remap_features.has(feature)) { + String remapped_path = config->get_value("remap", remap); + Vector<uint8_t> array = FileAccess::get_file_as_array(remapped_path); + err = p_func(p_udata, remapped_path, array, idx, total, enc_in_filters, enc_ex_filters, key); + } } } - } - if (err != OK) { - return err; - } + if (err != OK) { + return err; + } - //also save the .import file - Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import"); - err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key); + //also save the .import file + Vector<uint8_t> array = FileAccess::get_file_as_array(path + ".import"); + err = p_func(p_udata, path + ".import", array, idx, total, enc_in_filters, enc_ex_filters, key); - if (err != OK) { - return err; + if (err != OK) { + return err; + } } } else { + // Customize + bool do_export = true; for (int i = 0; i < export_plugins.size(); i++) { if (export_plugins[i]->get_script_instance()) { //script based - PackedStringArray features_psa; - for (const String &feature : features) { - features_psa.push_back(feature); - } export_plugins.write[i]->_export_file_script(path, type, features_psa); } else { export_plugins.write[i]->_export_file(path, type, features); @@ -748,8 +1154,18 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & } //just store it as it comes if (do_export) { - Vector<uint8_t> array = FileAccess::get_file_as_array(path); - err = p_func(p_udata, path, array, idx, total, enc_in_filters, enc_ex_filters, key); + // Customization only happens if plugins did not take care of it before + bool force_binary = convert_text_to_binary && (path.get_extension().to_lower() == "tres" || path.get_extension().to_lower() == "tscn"); + String export_path = _export_customize(path, customize_resources_plugins, customize_scenes_plugins, export_cache, export_base_path, force_binary); + + if (export_path != path) { + // Add a remap entry + path_remaps.push_back(path); + path_remaps.push_back(export_path); + } + + Vector<uint8_t> array = FileAccess::get_file_as_array(export_path); + err = p_func(p_udata, export_path, array, idx, total, enc_in_filters, enc_ex_filters, key); if (err != OK) { return err; } @@ -759,6 +1175,31 @@ Error EditorExportPlatform::export_project_files(const Ref<EditorExportPreset> & idx++; } + if (convert_text_to_binary || customize_resources_plugins.size() || customize_scenes_plugins.size()) { + // End scene customization + + String fcache = export_base_path.path_join("file_cache"); + Ref<FileAccess> f = FileAccess::open(fcache, FileAccess::WRITE); + + if (f.is_valid()) { + for (const KeyValue<String, FileExportCache> &E : export_cache) { + if (E.value.used) { // May be old, unused + String l = E.key + "::" + E.value.source_md5 + "::" + itos(E.value.source_modified_time) + "::" + E.value.saved_path; + f->store_line(l); + } + } + } else { + ERR_PRINT("Error opening export file cache: " + fcache); + } + + for (uint32_t i = 0; i < customize_resources_plugins.size(); i++) { + customize_resources_plugins[i]->_end_customize_resources(); + } + + for (uint32_t i = 0; i < customize_scenes_plugins.size(); i++) { + customize_scenes_plugins[i]->_end_customize_scenes(); + } + } //save config! Vector<String> custom_list; diff --git a/editor/export/editor_export_platform.h b/editor/export/editor_export_platform.h index bbdb47e041..93bc54284f 100644 --- a/editor/export/editor_export_platform.h +++ b/editor/export/editor_export_platform.h @@ -40,6 +40,8 @@ struct EditorProgress; #include "scene/gui/rich_text_label.h" #include "scene/main/node.h" +class EditorExportPlugin; + class EditorExportPlatform : public RefCounted { GDCLASS(EditorExportPlatform, RefCounted); @@ -99,6 +101,20 @@ private: static Error _add_shared_object(void *p_userdata, const SharedObject &p_so); + struct FileExportCache { + uint64_t source_modified_time = 0; + String source_md5; + String saved_path; + bool used = false; + }; + + bool _export_customize_dictionary(Dictionary &dict, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + bool _export_customize_array(Array &array, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + bool _export_customize_object(Object *p_object, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + bool _export_customize_scene_resources(Node *p_root, Node *p_node, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins); + + String _export_customize(const String &p_path, LocalVector<Ref<EditorExportPlugin>> &customize_resources_plugins, LocalVector<Ref<EditorExportPlugin>> &customize_scenes_plugins, HashMap<String, FileExportCache> &export_cache, const String &export_base_path, bool p_force_save); + protected: struct ExportNotifier { ExportNotifier(EditorExportPlatform &p_platform, const Ref<EditorExportPreset> &p_preset, bool p_debug, const String &p_path, int p_flags); diff --git a/editor/export/editor_export_plugin.cpp b/editor/export/editor_export_plugin.cpp index 27a671a919..971ea579cc 100644 --- a/editor/export/editor_export_plugin.cpp +++ b/editor/export/editor_export_plugin.cpp @@ -138,6 +138,64 @@ void EditorExportPlugin::_export_end_script() { GDVIRTUAL_CALL(_export_end); } +// Customization + +bool EditorExportPlugin::_begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const { + bool ret = false; + if (GDVIRTUAL_CALL(_begin_customize_resources, p_platform, p_features, ret)) { + return ret; + } + return false; +} + +Ref<Resource> EditorExportPlugin::_customize_resource(const Ref<Resource> &p_resource, const String &p_path) { + Ref<Resource> ret; + if (GDVIRTUAL_REQUIRED_CALL(_customize_resource, p_resource, p_path, ret)) { + return ret; + } + return Ref<Resource>(); +} + +bool EditorExportPlugin::_begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const { + bool ret = false; + if (GDVIRTUAL_CALL(_begin_customize_scenes, p_platform, p_features, ret)) { + return ret; + } + return false; +} + +Node *EditorExportPlugin::_customize_scene(Node *p_root, const String &p_path) { + Node *ret = nullptr; + if (GDVIRTUAL_REQUIRED_CALL(_customize_scene, p_root, p_path, ret)) { + return ret; + } + return nullptr; +} + +uint64_t EditorExportPlugin::_get_customization_configuration_hash() const { + uint64_t ret = 0; + if (GDVIRTUAL_REQUIRED_CALL(_get_customization_configuration_hash, ret)) { + return ret; + } + return 0; +} + +void EditorExportPlugin::_end_customize_scenes() { + GDVIRTUAL_CALL(_end_customize_scenes); +} + +void EditorExportPlugin::_end_customize_resources() { + GDVIRTUAL_CALL(_end_customize_resources); +} + +String EditorExportPlugin::_get_name() const { + String ret; + if (GDVIRTUAL_REQUIRED_CALL(_get_name, ret)) { + return ret; + } + return ""; +} + void EditorExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { } @@ -164,38 +222,20 @@ void EditorExportPlugin::_bind_methods() { GDVIRTUAL_BIND(_export_file, "path", "type", "features"); GDVIRTUAL_BIND(_export_begin, "features", "is_debug", "path", "flags"); GDVIRTUAL_BIND(_export_end); -} -EditorExportPlugin::EditorExportPlugin() { -} + GDVIRTUAL_BIND(_begin_customize_resources, "platform", "features"); + GDVIRTUAL_BIND(_customize_resource, "resource", "path"); -/////////////////////// + GDVIRTUAL_BIND(_begin_customize_scenes, "platform", "features"); + GDVIRTUAL_BIND(_customize_scene, "scene", "path"); -void EditorExportTextSceneToBinaryPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { - String extension = p_path.get_extension().to_lower(); - if (extension != "tres" && extension != "tscn") { - return; - } + GDVIRTUAL_BIND(_get_customization_configuration_hash); - bool convert = GLOBAL_GET("editor/export/convert_text_resources_to_binary"); - if (!convert) { - return; - } - String tmp_path = EditorPaths::get_singleton()->get_cache_dir().path_join("tmpfile.res"); - Error err = ResourceFormatLoaderText::convert_file_to_binary(p_path, tmp_path); - if (err != OK) { - DirAccess::remove_file_or_error(tmp_path); - ERR_FAIL(); - } - Vector<uint8_t> data = FileAccess::get_file_as_array(tmp_path); - if (data.size() == 0) { - DirAccess::remove_file_or_error(tmp_path); - ERR_FAIL(); - } - DirAccess::remove_file_or_error(tmp_path); - add_file(p_path + ".converted.res", data, true); + GDVIRTUAL_BIND(_end_customize_scenes); + GDVIRTUAL_BIND(_end_customize_resources); + + GDVIRTUAL_BIND(_get_name); } -EditorExportTextSceneToBinaryPlugin::EditorExportTextSceneToBinaryPlugin() { - GLOBAL_DEF("editor/export/convert_text_resources_to_binary", false); +EditorExportPlugin::EditorExportPlugin() { } diff --git a/editor/export/editor_export_plugin.h b/editor/export/editor_export_plugin.h index 04ebc1dfed..3f37ed40be 100644 --- a/editor/export/editor_export_plugin.h +++ b/editor/export/editor_export_plugin.h @@ -34,6 +34,7 @@ #include "core/extension/native_extension.h" #include "editor_export_preset.h" #include "editor_export_shared_object.h" +#include "scene/main/node.h" class EditorExportPlugin : public RefCounted { GDCLASS(EditorExportPlugin, RefCounted); @@ -77,6 +78,7 @@ class EditorExportPlugin : public RefCounted { macos_plugin_files.clear(); } + // Export void _export_file_script(const String &p_path, const String &p_type, const Vector<String> &p_features); void _export_begin_script(const Vector<String> &p_features, bool p_debug, const String &p_path, int p_flags); void _export_end_script(); @@ -108,6 +110,31 @@ protected: GDVIRTUAL4(_export_begin, Vector<String>, bool, String, uint32_t) GDVIRTUAL0(_export_end) + GDVIRTUAL2RC(bool, _begin_customize_resources, const Ref<EditorExportPlatform> &, const Vector<String> &) + GDVIRTUAL2R(Ref<Resource>, _customize_resource, const Ref<Resource> &, String) + + GDVIRTUAL2RC(bool, _begin_customize_scenes, const Ref<EditorExportPlatform> &, const Vector<String> &) + GDVIRTUAL2R(Node *, _customize_scene, Node *, String) + GDVIRTUAL0RC(uint64_t, _get_customization_configuration_hash) + + GDVIRTUAL0(_end_customize_scenes) + GDVIRTUAL0(_end_customize_resources) + + GDVIRTUAL0RC(String, _get_name) + + bool _begin_customize_resources(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const; // Return true if this plugin does property export customization + Ref<Resource> _customize_resource(const Ref<Resource> &p_resource, const String &p_path); // If nothing is returned, it means do not touch (nothing changed). If something is returned (either the same or a different resource) it means changes are made. + + bool _begin_customize_scenes(const Ref<EditorExportPlatform> &p_platform, const Vector<String> &p_features) const; // Return true if this plugin does property export customization + Node *_customize_scene(Node *p_root, const String &p_path); // Return true if a change was made + + uint64_t _get_customization_configuration_hash() const; // Hash used for caching customized resources and scenes. + + void _end_customize_scenes(); + void _end_customize_resources(); + + virtual String _get_name() const; + public: Vector<String> get_ios_frameworks() const; Vector<String> get_ios_embedded_frameworks() const; @@ -121,12 +148,4 @@ public: EditorExportPlugin(); }; -class EditorExportTextSceneToBinaryPlugin : public EditorExportPlugin { - GDCLASS(EditorExportTextSceneToBinaryPlugin, EditorExportPlugin); - -public: - virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) override; - EditorExportTextSceneToBinaryPlugin(); -}; - #endif // EDITOR_EXPORT_PLUGIN_H diff --git a/editor/icons/NavigationLink2D.svg b/editor/icons/NavigationLink2D.svg new file mode 100644 index 0000000000..6c5f17e256 --- /dev/null +++ b/editor/icons/NavigationLink2D.svg @@ -0,0 +1,4 @@ +<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m12.386 5.3097c-0.69157-0.021112-1.3071 0.36382-1.7492 0.86685-0.58 0.58-1.16 1.16-1.74 1.74 0.4588-0.28502 1.0599-0.064948 1.4771-0.037996 0.45549-0.44357 0.89024-0.91006 1.3596-1.3383 0.56256-0.44564 1.4906-0.15731 1.7028 0.52802 0.18967 0.4871-0.049221 1.0098-0.43284 1.3208-0.70048 0.68896-1.3789 1.4022-2.0935 2.0755-0.47999 0.3725-1.2044 0.226-1.5679-0.24034-0.38763-0.38194-1.0641 0.16031-0.78317 0.6241 0.6767 0.94379 2.1573 1.1282 3.0411 0.36751 0.80287-0.7704 1.5793-1.5696 2.3665-2.3564 0.79925-0.83719 0.70104-2.3112-0.19552-3.0393-0.38108-0.32877-0.8822-0.5119-1.385-0.51049zm-3.051 3.051c-0.69157-0.021106-1.3071 0.36382-1.7492 0.86685-0.67513 0.68452-1.37 1.3506-2.0319 2.0474-0.75433 0.87744-0.58087 2.3428 0.34933 3.0252 0.84748 0.68613 2.192 0.54839 2.8998-0.27341 0.63032-0.63031 1.2606-1.2606 1.8909-1.8909-0.4587 0.28554-1.0602 0.0659-1.477 0.038069-0.45445 0.44348-0.88773 0.91034-1.3564 1.3383-0.56256 0.44565-1.4906 0.15731-1.7028-0.52802-0.18967-0.4871 0.049229-1.0098 0.43284-1.3208 0.70048-0.68896 1.3789-1.4022 2.0935-2.0755 0.48-0.3725 1.2044-0.22601 1.5679 0.24036 0.38733 0.38325 1.064-0.16067 0.78313-0.6241-0.39353-0.52481-1.0429-0.84871-1.7002-0.8434z" fill="#8ea6f4" fill-opacity=".99608" stroke-linecap="round" stroke-linejoin="round" stroke-width=".013911"/> +<path d="m2 1c-0.61942-0.0066969-1.0877 0.60314-1 1.198 0.00345 3.968-0.006897 7.9364 0.00517 11.904 0.043388 0.62851 0.69346 0.98513 1.272 0.89776h2.5896c-0.77174-0.5015-1.2078-1.2613-1.3143-2.3356-0.11601-1.1701 0.63729-2.024 1.6748-3.1566 0.65335-0.71326 1.4757-1.5822 2.3587-2.3316 0.76308-0.64765 1.7509-1.679 2.9376-2.578 0.91259-0.69136 2.2893-0.74691 3.1014-0.33143 0.91184 0.46649 1.2635 1.1209 1.4067 1.3826-0.0052-2.335-0.02135-1.3526-0.03955-3.6863 5e-3 -0.64349-0.67497-1.0568-1.2694-0.96289z" fill="#8ea6f4" fill-opacity=".99608"/> +</svg> diff --git a/editor/icons/NavigationLink3D.svg b/editor/icons/NavigationLink3D.svg new file mode 100644 index 0000000000..ea4092c2c7 --- /dev/null +++ b/editor/icons/NavigationLink3D.svg @@ -0,0 +1,4 @@ +<svg version="1.1" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"> +<path d="m12.386 5.3097c-0.69157-0.021112-1.3071 0.36382-1.7492 0.86685-0.58 0.58-1.16 1.16-1.74 1.74 0.4588-0.28502 1.0599-0.064948 1.4771-0.037996 0.45549-0.44357 0.89024-0.91006 1.3596-1.3383 0.56256-0.44564 1.4906-0.15731 1.7028 0.52802 0.18967 0.4871-0.049221 1.0098-0.43284 1.3208-0.70048 0.68896-1.3789 1.4022-2.0935 2.0755-0.47999 0.3725-1.2044 0.226-1.5679-0.24034-0.38763-0.38194-1.0641 0.16031-0.78317 0.6241 0.6767 0.94379 2.1573 1.1282 3.0411 0.36751 0.80287-0.7704 1.5793-1.5696 2.3665-2.3564 0.79925-0.83719 0.70104-2.3112-0.19552-3.0393-0.38108-0.32877-0.8822-0.5119-1.385-0.51049zm-3.051 3.051c-0.69157-0.021106-1.3071 0.36382-1.7492 0.86685-0.67513 0.68452-1.37 1.3506-2.0319 2.0474-0.75433 0.87744-0.58087 2.3428 0.34933 3.0252 0.84748 0.68613 2.192 0.54839 2.8998-0.27341 0.63032-0.63031 1.2606-1.2606 1.8909-1.8909-0.4587 0.28554-1.0602 0.0659-1.477 0.038069-0.45445 0.44348-0.88773 0.91034-1.3564 1.3383-0.56256 0.44565-1.4906 0.15731-1.7028-0.52802-0.18967-0.4871 0.049229-1.0098 0.43284-1.3208 0.70048-0.68896 1.3789-1.4022 2.0935-2.0755 0.48-0.3725 1.2044-0.22601 1.5679 0.24036 0.38733 0.38325 1.064-0.16067 0.78313-0.6241-0.39353-0.52481-1.0429-0.84871-1.7002-0.8434z" fill="#fc7e7e" fill-opacity=".99608" stroke-linecap="round" stroke-linejoin="round" stroke-width=".013911"/> +<path d="m2 1c-0.61942-0.0066969-1.0877 0.60314-1 1.198 0.00345 3.968-0.006897 7.9364 0.00517 11.904 0.043388 0.62851 0.69346 0.98513 1.272 0.89776h2.5896c-0.77174-0.5015-1.2078-1.2613-1.3143-2.3356-0.11601-1.1701 0.63729-2.024 1.6748-3.1566 0.65335-0.71326 1.4757-1.5822 2.3587-2.3316 0.76308-0.64765 1.7509-1.679 2.9376-2.578 0.91259-0.69136 2.2893-0.74691 3.1014-0.33143 0.91184 0.46649 1.2635 1.1209 1.4067 1.3826-0.0052-2.335-0.02135-1.3526-0.03955-3.6863 5e-3 -0.64349-0.67497-1.0568-1.2694-0.96289z" fill="#fc7d7d" fill-opacity=".99608"/> +</svg> diff --git a/editor/import/resource_importer_bitmask.cpp b/editor/import/resource_importer_bitmask.cpp index c03962b8a4..577a4c32b3 100644 --- a/editor/import/resource_importer_bitmask.cpp +++ b/editor/import/resource_importer_bitmask.cpp @@ -99,7 +99,7 @@ Error ResourceImporterBitMap::import(const String &p_source_file, const String & bit = c.a > threshold; } - bitmap->set_bit(Vector2(j, i), bit); + bitmap->set_bit(j, i, bit); } } diff --git a/editor/plugins/editor_preview_plugins.cpp b/editor/plugins/editor_preview_plugins.cpp index 369ab0745e..0ec36736bd 100644 --- a/editor/plugins/editor_preview_plugins.cpp +++ b/editor/plugins/editor_preview_plugins.cpp @@ -201,7 +201,7 @@ Ref<Texture2D> EditorBitmapPreviewPlugin::generate(const Ref<Resource> &p_from, for (int i = 0; i < bm->get_size().width; i++) { for (int j = 0; j < bm->get_size().height; j++) { - if (bm->get_bit(Point2i(i, j))) { + if (bm->get_bit(i, j)) { w[j * (int)bm->get_size().width + i] = 255; } else { w[j * (int)bm->get_size().width + i] = 0; diff --git a/editor/plugins/gdextension_export_plugin.h b/editor/plugins/gdextension_export_plugin.h index b5eca46ad3..e1d68d97b5 100644 --- a/editor/plugins/gdextension_export_plugin.h +++ b/editor/plugins/gdextension_export_plugin.h @@ -36,6 +36,7 @@ class GDExtensionExportPlugin : public EditorExportPlugin { protected: virtual void _export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features); + virtual String _get_name() const { return "GDExtension"; } }; void GDExtensionExportPlugin::_export_file(const String &p_path, const String &p_type, const HashSet<String> &p_features) { diff --git a/editor/plugins/navigation_link_2d_editor_plugin.cpp b/editor/plugins/navigation_link_2d_editor_plugin.cpp new file mode 100644 index 0000000000..b72f639fbf --- /dev/null +++ b/editor/plugins/navigation_link_2d_editor_plugin.cpp @@ -0,0 +1,191 @@ +/*************************************************************************/ +/* navigation_link_2d_editor_plugin.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 "navigation_link_2d_editor_plugin.h" + +#include "canvas_item_editor_plugin.h" +#include "editor/editor_node.h" +#include "editor/editor_settings.h" +#include "editor/editor_undo_redo_manager.h" +#include "servers/navigation_server_3d.h" + +void NavigationLink2DEditor::_notification(int p_what) { + switch (p_what) { + case NOTIFICATION_ENTER_TREE: { + get_tree()->connect("node_removed", callable_mp(this, &NavigationLink2DEditor::_node_removed)); + } break; + + case NOTIFICATION_EXIT_TREE: { + get_tree()->disconnect("node_removed", callable_mp(this, &NavigationLink2DEditor::_node_removed)); + } break; + } +} + +void NavigationLink2DEditor::_node_removed(Node *p_node) { + if (p_node == node) { + node = nullptr; + } +} + +bool NavigationLink2DEditor::forward_canvas_gui_input(const Ref<InputEvent> &p_event) { + if (!node || !node->is_visible_in_tree()) { + return false; + } + + real_t grab_threshold = EDITOR_GET("editors/polygon_editor/point_grab_radius"); + Transform2D xform = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + + Ref<InputEventMouseButton> mb = p_event; + if (mb.is_valid() && mb->get_button_index() == MouseButton::LEFT) { + if (mb->is_pressed()) { + // Start location + if (xform.xform(node->get_start_location()).distance_to(mb->get_position()) < grab_threshold) { + start_grabbed = true; + original_start_location = node->get_start_location(); + + return true; + } else { + start_grabbed = false; + } + + // End location + if (xform.xform(node->get_end_location()).distance_to(mb->get_position()) < grab_threshold) { + end_grabbed = true; + original_end_location = node->get_end_location(); + + return true; + } else { + end_grabbed = false; + } + } else { + if (start_grabbed) { + undo_redo->create_action(TTR("Set start_location")); + undo_redo->add_do_method(node, "set_start_location", node->get_start_location()); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(node, "set_start_location", original_start_location); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + + start_grabbed = false; + + return true; + } + + if (end_grabbed) { + undo_redo->create_action(TTR("Set end_location")); + undo_redo->add_do_method(node, "set_end_location", node->get_end_location()); + undo_redo->add_do_method(canvas_item_editor, "update_viewport"); + undo_redo->add_undo_method(node, "set_end_location", original_end_location); + undo_redo->add_undo_method(canvas_item_editor, "update_viewport"); + undo_redo->commit_action(); + + end_grabbed = false; + + return true; + } + } + } + + Ref<InputEventMouseMotion> mm = p_event; + if (mm.is_valid()) { + Vector2 point = canvas_item_editor->snap_point(canvas_item_editor->get_canvas_transform().affine_inverse().xform(mm->get_position())); + point = node->get_global_transform().affine_inverse().xform(point); + + if (start_grabbed) { + node->set_start_location(point); + canvas_item_editor->update_viewport(); + + return true; + } + + if (end_grabbed) { + node->set_end_location(point); + canvas_item_editor->update_viewport(); + + return true; + } + } + + return false; +} + +void NavigationLink2DEditor::forward_canvas_draw_over_viewport(Control *p_overlay) { + if (!node || !node->is_visible_in_tree()) { + return; + } + + Transform2D gt = canvas_item_editor->get_canvas_transform() * node->get_global_transform(); + Vector2 global_start_location = gt.xform(node->get_start_location()); + Vector2 global_end_location = gt.xform(node->get_end_location()); + + // Only drawing the handles here, since the debug rendering will fill in the rest. + const Ref<Texture2D> handle = get_theme_icon(SNAME("EditorHandle"), SNAME("EditorIcons")); + p_overlay->draw_texture(handle, global_start_location - handle->get_size() / 2); + p_overlay->draw_texture(handle, global_end_location - handle->get_size() / 2); +} + +void NavigationLink2DEditor::edit(NavigationLink2D *p_node) { + if (!canvas_item_editor) { + canvas_item_editor = CanvasItemEditor::get_singleton(); + } + + if (p_node) { + node = p_node; + } else { + node = nullptr; + } + + canvas_item_editor->update_viewport(); +} + +NavigationLink2DEditor::NavigationLink2DEditor() { + undo_redo = EditorNode::get_undo_redo(); +} + +/////////////////////// + +void NavigationLink2DEditorPlugin::edit(Object *p_object) { + editor->edit(Object::cast_to<NavigationLink2D>(p_object)); +} + +bool NavigationLink2DEditorPlugin::handles(Object *p_object) const { + return Object::cast_to<NavigationLink2D>(p_object) != nullptr; +} + +void NavigationLink2DEditorPlugin::make_visible(bool p_visible) { + if (!p_visible) { + edit(nullptr); + } +} + +NavigationLink2DEditorPlugin::NavigationLink2DEditorPlugin() { + editor = memnew(NavigationLink2DEditor); + EditorNode::get_singleton()->get_gui_base()->add_child(editor); +} diff --git a/editor/plugins/navigation_link_2d_editor_plugin.h b/editor/plugins/navigation_link_2d_editor_plugin.h new file mode 100644 index 0000000000..1c1251bec7 --- /dev/null +++ b/editor/plugins/navigation_link_2d_editor_plugin.h @@ -0,0 +1,83 @@ +/*************************************************************************/ +/* navigation_link_2d_editor_plugin.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 NAVIGATION_LINK_2D_EDITOR_PLUGIN_H +#define NAVIGATION_LINK_2D_EDITOR_PLUGIN_H + +#include "editor/editor_plugin.h" +#include "scene/2d/navigation_link_2d.h" + +class CanvasItemEditor; +class EditorUndoRedoManager; + +class NavigationLink2DEditor : public Control { + GDCLASS(NavigationLink2DEditor, Control); + + Ref<EditorUndoRedoManager> undo_redo; + CanvasItemEditor *canvas_item_editor = nullptr; + NavigationLink2D *node; + + bool start_grabbed = false; + Vector2 original_start_location; + + bool end_grabbed = false; + Vector2 original_end_location; + +protected: + void _notification(int p_what); + void _node_removed(Node *p_node); + +public: + bool forward_canvas_gui_input(const Ref<InputEvent> &p_event); + void forward_canvas_draw_over_viewport(Control *p_overlay); + void edit(NavigationLink2D *p_node); + + NavigationLink2DEditor(); +}; + +class NavigationLink2DEditorPlugin : public EditorPlugin { + GDCLASS(NavigationLink2DEditorPlugin, EditorPlugin); + + NavigationLink2DEditor *editor = nullptr; + +public: + virtual bool forward_canvas_gui_input(const Ref<InputEvent> &p_event) override { return editor->forward_canvas_gui_input(p_event); } + virtual void forward_canvas_draw_over_viewport(Control *p_overlay) override { editor->forward_canvas_draw_over_viewport(p_overlay); } + + virtual String get_name() const override { return "NavigationLink2D"; } + bool has_main_screen() const override { return false; } + virtual void edit(Object *p_object) override; + virtual bool handles(Object *p_object) const override; + virtual void make_visible(bool p_visible) override; + + NavigationLink2DEditorPlugin(); +}; + +#endif // NAVIGATION_LINK_2D_EDITOR_PLUGIN_H diff --git a/editor/plugins/node_3d_editor_gizmos.cpp b/editor/plugins/node_3d_editor_gizmos.cpp index 0c27ed46c5..ec6ea7f39b 100644 --- a/editor/plugins/node_3d_editor_gizmos.cpp +++ b/editor/plugins/node_3d_editor_gizmos.cpp @@ -54,6 +54,7 @@ #include "scene/3d/lightmap_probe.h" #include "scene/3d/marker_3d.h" #include "scene/3d/mesh_instance_3d.h" +#include "scene/3d/navigation_link_3d.h" #include "scene/3d/navigation_region_3d.h" #include "scene/3d/occluder_instance_3d.h" #include "scene/3d/ray_cast_3d.h" @@ -4999,6 +5000,175 @@ void NavigationRegion3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { } } +//// + +NavigationLink3DGizmoPlugin::NavigationLink3DGizmoPlugin() { + create_material("navigation_link_material", NavigationServer3D::get_singleton()->get_debug_navigation_link_connection_color()); + create_material("navigation_link_material_disabled", NavigationServer3D::get_singleton()->get_debug_navigation_link_connection_disabled_color()); + create_handle_material("handles"); +} + +bool NavigationLink3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<NavigationLink3D>(p_spatial) != nullptr; +} + +String NavigationLink3DGizmoPlugin::get_gizmo_name() const { + return "NavigationLink3D"; +} + +int NavigationLink3DGizmoPlugin::get_priority() const { + return -1; +} + +void NavigationLink3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + + RID nav_map = link->get_world_3d()->get_navigation_map(); + real_t search_radius = NavigationServer3D::get_singleton()->map_get_link_connection_radius(nav_map); + Vector3 up_vector = NavigationServer3D::get_singleton()->map_get_up(nav_map); + Vector3::Axis up_axis = up_vector.max_axis_index(); + + Vector3 start_location = link->get_start_location(); + Vector3 end_location = link->get_end_location(); + + Ref<Material> link_material = get_material("navigation_link_material", p_gizmo); + Ref<Material> link_material_disabled = get_material("navigation_link_material_disabled", p_gizmo); + Ref<Material> handles_material = get_material("handles"); + + p_gizmo->clear(); + + // Draw line between the points. + Vector<Vector3> lines; + lines.append(start_location); + lines.append(end_location); + + // Draw start location search radius + for (int i = 0; i < 30; i++) { + // Create a circle + const float ra = Math::deg_to_rad((float)(i * 12)); + const float rb = Math::deg_to_rad((float)((i + 1) * 12)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + + // Draw axis-aligned circle + switch (up_axis) { + case Vector3::AXIS_X: + lines.append(start_location + Vector3(0, a.x, a.y)); + lines.append(start_location + Vector3(0, b.x, b.y)); + break; + case Vector3::AXIS_Y: + lines.append(start_location + Vector3(a.x, 0, a.y)); + lines.append(start_location + Vector3(b.x, 0, b.y)); + break; + case Vector3::AXIS_Z: + lines.append(start_location + Vector3(a.x, a.y, 0)); + lines.append(start_location + Vector3(b.x, b.y, 0)); + break; + } + } + + // Draw end location search radius + for (int i = 0; i < 30; i++) { + // Create a circle + const float ra = Math::deg_to_rad((float)(i * 12)); + const float rb = Math::deg_to_rad((float)((i + 1) * 12)); + const Point2 a = Vector2(Math::sin(ra), Math::cos(ra)) * search_radius; + const Point2 b = Vector2(Math::sin(rb), Math::cos(rb)) * search_radius; + + // Draw axis-aligned circle + switch (up_axis) { + case Vector3::AXIS_X: + lines.append(end_location + Vector3(0, a.x, a.y)); + lines.append(end_location + Vector3(0, b.x, b.y)); + break; + case Vector3::AXIS_Y: + lines.append(end_location + Vector3(a.x, 0, a.y)); + lines.append(end_location + Vector3(b.x, 0, b.y)); + break; + case Vector3::AXIS_Z: + lines.append(end_location + Vector3(a.x, a.y, 0)); + lines.append(end_location + Vector3(b.x, b.y, 0)); + break; + } + } + + p_gizmo->add_lines(lines, link->is_enabled() ? link_material : link_material_disabled); + p_gizmo->add_collision_segments(lines); + + Vector<Vector3> handles; + handles.append(start_location); + handles.append(end_location); + p_gizmo->add_handles(handles, handles_material); +} + +String NavigationLink3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + return p_id == 0 ? TTR("Start Location") : TTR("End Location"); +} + +Variant NavigationLink3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + return p_id == 0 ? link->get_start_location() : link->get_end_location(); +} + +void NavigationLink3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + + Transform3D gt = link->get_global_transform(); + Transform3D gi = gt.affine_inverse(); + + Transform3D ct = p_camera->get_global_transform(); + Vector3 cam_dir = ct.basis.get_column(Vector3::AXIS_Z); + + Vector3 ray_from = p_camera->project_ray_origin(p_point); + Vector3 ray_dir = p_camera->project_ray_normal(p_point); + + Vector3 location = p_id == 0 ? link->get_start_location() : link->get_end_location(); + Plane move_plane = Plane(cam_dir, gt.xform(location)); + + Vector3 intersection; + if (!move_plane.intersects_ray(ray_from, ray_dir, &intersection)) { + return; + } + + if (Node3DEditor::get_singleton()->is_snap_enabled()) { + double snap = Node3DEditor::get_singleton()->get_translate_snap(); + intersection.snap(Vector3(snap, snap, snap)); + } + + location = gi.xform(intersection); + if (p_id == 0) { + link->set_start_location(location); + } else if (p_id == 1) { + link->set_end_location(location); + } +} + +void NavigationLink3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + NavigationLink3D *link = Object::cast_to<NavigationLink3D>(p_gizmo->get_spatial_node()); + + if (p_cancel) { + if (p_id == 0) { + link->set_start_location(p_restore); + } else { + link->set_end_location(p_restore); + } + return; + } + + Ref<EditorUndoRedoManager> &ur = EditorNode::get_undo_redo(); + if (p_id == 0) { + ur->create_action(TTR("Change Start Location")); + ur->add_do_method(link, "set_start_location", link->get_start_location()); + ur->add_undo_method(link, "set_start_location", p_restore); + } else { + ur->create_action(TTR("Change End Location")); + ur->add_do_method(link, "set_end_location", link->get_end_location()); + ur->add_undo_method(link, "set_end_location", p_restore); + } + + ur->commit_action(); +} + ////// #define BODY_A_RADIUS 0.25 diff --git a/editor/plugins/node_3d_editor_gizmos.h b/editor/plugins/node_3d_editor_gizmos.h index 1b6485ac4e..5924f8571a 100644 --- a/editor/plugins/node_3d_editor_gizmos.h +++ b/editor/plugins/node_3d_editor_gizmos.h @@ -631,6 +631,23 @@ public: NavigationRegion3DGizmoPlugin(); }; +class NavigationLink3DGizmoPlugin : public EditorNode3DGizmoPlugin { + GDCLASS(NavigationLink3DGizmoPlugin, EditorNode3DGizmoPlugin); + +public: + bool has_gizmo(Node3D *p_spatial) override; + String get_gizmo_name() const override; + int get_priority() const override; + void redraw(EditorNode3DGizmo *p_gizmo) override; + + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + + NavigationLink3DGizmoPlugin(); +}; + class JointGizmosDrawer { public: static Basis look_body(const Transform3D &p_joint_transform, const Transform3D &p_body_transform); diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index 1a704a5777..0bb044e679 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -7523,6 +7523,7 @@ void Node3DEditor::_register_all_gizmos() { add_gizmo_plugin(Ref<CollisionObject3DGizmoPlugin>(memnew(CollisionObject3DGizmoPlugin))); add_gizmo_plugin(Ref<CollisionShape3DGizmoPlugin>(memnew(CollisionShape3DGizmoPlugin))); add_gizmo_plugin(Ref<CollisionPolygon3DGizmoPlugin>(memnew(CollisionPolygon3DGizmoPlugin))); + add_gizmo_plugin(Ref<NavigationLink3DGizmoPlugin>(memnew(NavigationLink3DGizmoPlugin))); add_gizmo_plugin(Ref<NavigationRegion3DGizmoPlugin>(memnew(NavigationRegion3DGizmoPlugin))); add_gizmo_plugin(Ref<Joint3DGizmoPlugin>(memnew(Joint3DGizmoPlugin))); add_gizmo_plugin(Ref<PhysicalBone3DGizmoPlugin>(memnew(PhysicalBone3DGizmoPlugin))); |