diff options
-rw-r--r-- | core/io/image.cpp | 22 | ||||
-rw-r--r-- | core/io/image.h | 2 | ||||
-rw-r--r-- | editor/editor_settings.cpp | 2 | ||||
-rw-r--r-- | editor/editor_themes.cpp | 22 | ||||
-rw-r--r-- | editor/plugins/sprite_2d_editor_plugin.cpp | 5 | ||||
-rw-r--r-- | editor/scene_tree_dock.cpp | 198 | ||||
-rw-r--r-- | editor/scene_tree_dock.h | 14 | ||||
-rw-r--r-- | scene/main/node.cpp | 57 | ||||
-rw-r--r-- | scene/main/node.h | 3 | ||||
-rw-r--r-- | servers/physics_3d/collision_solver_3d_sat.cpp | 279 |
10 files changed, 449 insertions, 155 deletions
diff --git a/core/io/image.cpp b/core/io/image.cpp index 986c29b539..5d46d75efe 100644 --- a/core/io/image.cpp +++ b/core/io/image.cpp @@ -2985,6 +2985,26 @@ void Image::set_pixel(int p_x, int p_y, const Color &p_color) { _set_color_at_ofs(data.ptrw(), ofs, p_color); } +void Image::adjust_bcs(float p_brightness, float p_contrast, float p_saturation) { + uint8_t *w = data.ptrw(); + uint32_t pixel_size = get_format_pixel_size(format); + uint32_t pixel_count = data.size() / pixel_size; + + for (uint32_t i = 0; i < pixel_count; i++) { + Color c = _get_color_at_ofs(w, i); + Vector3 rgb(c.r, c.g, c.b); + + rgb *= p_brightness; + rgb = Vector3(0.5, 0.5, 0.5).lerp(rgb, p_contrast); + float center = (rgb.x + rgb.y + rgb.z) / 3.0; + rgb = Vector3(center, center, center).lerp(rgb, p_saturation); + c.r = rgb.x; + c.g = rgb.y; + c.b = rgb.z; + _set_color_at_ofs(w, i, c); + } +} + Image::UsedChannels Image::detect_used_channels(CompressSource p_source) { ERR_FAIL_COND_V(data.size() == 0, USED_CHANNELS_RGBA); ERR_FAIL_COND_V(is_compressed(), USED_CHANNELS_RGBA); @@ -3132,6 +3152,8 @@ void Image::_bind_methods() { ClassDB::bind_method(D_METHOD("set_pixelv", "point", "color"), &Image::set_pixelv); ClassDB::bind_method(D_METHOD("set_pixel", "x", "y", "color"), &Image::set_pixel); + ClassDB::bind_method(D_METHOD("adjust_bcs", "brightness", "contrast", "saturation"), &Image::adjust_bcs); + ClassDB::bind_method(D_METHOD("load_png_from_buffer", "buffer"), &Image::load_png_from_buffer); ClassDB::bind_method(D_METHOD("load_jpg_from_buffer", "buffer"), &Image::load_jpg_from_buffer); ClassDB::bind_method(D_METHOD("load_webp_from_buffer", "buffer"), &Image::load_webp_from_buffer); diff --git a/core/io/image.h b/core/io/image.h index b894be7df4..df8f9b35a1 100644 --- a/core/io/image.h +++ b/core/io/image.h @@ -390,6 +390,8 @@ public: void set_pixelv(const Point2i &p_point, const Color &p_color); void set_pixel(int p_x, int p_y, const Color &p_color); + void adjust_bcs(float p_brightness, float p_contrast, float p_saturation); + void set_as_black(); void copy_internals_from(const Ref<Image> &p_image) { diff --git a/editor/editor_settings.cpp b/editor/editor_settings.cpp index e37173bf68..6026181615 100644 --- a/editor/editor_settings.cpp +++ b/editor/editor_settings.cpp @@ -392,6 +392,8 @@ void EditorSettings::_load_defaults(Ref<ConfigFile> p_extra_config) { hints["interface/theme/accent_color"] = PropertyInfo(Variant::COLOR, "interface/theme/accent_color", PROPERTY_HINT_NONE, "", PROPERTY_USAGE_DEFAULT); _initial_set("interface/theme/contrast", 0.25); hints["interface/theme/contrast"] = PropertyInfo(Variant::FLOAT, "interface/theme/contrast", PROPERTY_HINT_RANGE, "0.01, 1, 0.01"); + _initial_set("interface/theme/icon_saturation", 1.0); + hints["interface/theme/icon_saturation"] = PropertyInfo(Variant::FLOAT, "interface/theme/icon_saturation", PROPERTY_HINT_RANGE, "0,2,0.01", PROPERTY_USAGE_DEFAULT); _initial_set("interface/theme/relationship_line_opacity", 0.1); hints["interface/theme/relationship_line_opacity"] = PropertyInfo(Variant::FLOAT, "interface/theme/relationship_line_opacity", PROPERTY_HINT_RANGE, "0.00, 1, 0.01"); _initial_set("interface/theme/highlight_tabs", false); diff --git a/editor/editor_themes.cpp b/editor/editor_themes.cpp index f81087ded9..aef069331b 100644 --- a/editor/editor_themes.cpp +++ b/editor/editor_themes.cpp @@ -106,7 +106,7 @@ static Ref<Texture2D> flip_icon(Ref<Texture2D> p_texture, bool p_flip_y = false, } #ifdef MODULE_SVG_ENABLED -static Ref<ImageTexture> editor_generate_icon(int p_index, bool p_convert_color, float p_scale = EDSCALE, bool p_force_filter = false) { +static Ref<ImageTexture> editor_generate_icon(int p_index, bool p_convert_color, float p_scale = EDSCALE, float p_saturation = 1.0) { Ref<ImageTexture> icon = memnew(ImageTexture); Ref<Image> img = memnew(Image); @@ -116,6 +116,9 @@ static Ref<ImageTexture> editor_generate_icon(int p_index, bool p_convert_color, const bool upsample = !Math::is_equal_approx(Math::round(p_scale), p_scale); ImageLoaderSVG::create_image_from_string(img, editor_icons_sources[p_index], p_scale, upsample, p_convert_color); + if (p_saturation != 1.0) { + img->adjust_bcs(1.0, 1.0, p_saturation); + } icon->create_from_image(img); // in this case filter really helps return icon; @@ -126,7 +129,7 @@ static Ref<ImageTexture> editor_generate_icon(int p_index, bool p_convert_color, #define ADD_CONVERT_COLOR(dictionary, old_color, new_color) dictionary[Color::html(old_color)] = Color::html(new_color) #endif -void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = true, int p_thumb_size = 32, bool p_only_thumbs = false) { +void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = true, int p_thumb_size = 32, bool p_only_thumbs = false, float p_icon_saturation = 1.0) { #ifdef MODULE_SVG_ENABLED // The default icon theme is designed to be used for a dark theme. // This dictionary stores color codes to convert to other colors @@ -239,14 +242,19 @@ void editor_register_and_generate_icons(Ref<Theme> p_theme, bool p_dark_theme = if (!p_only_thumbs) { for (int i = 0; i < editor_icons_count; i++) { float icon_scale = EDSCALE; + float saturation = p_icon_saturation; // Always keep the DefaultProjectIcon at the default size if (strcmp(editor_icons_names[i], "DefaultProjectIcon") == 0) { icon_scale = 1.0f; } + if (strcmp(editor_icons_names[i], "DefaultProjectIcon") == 0 || strcmp(editor_icons_names[i], "Godot") == 0 || strcmp(editor_icons_names[i], "Logo") == 0) { + saturation = 1.0; + } + const int is_exception = exceptions.has(editor_icons_names[i]); - const Ref<ImageTexture> icon = editor_generate_icon(i, !is_exception, icon_scale); + const Ref<ImageTexture> icon = editor_generate_icon(i, !is_exception, icon_scale, saturation); p_theme->set_icon(editor_icons_names[i], "EditorIcons", icon); } @@ -290,6 +298,7 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { Color accent_color = EDITOR_GET("interface/theme/accent_color"); Color base_color = EDITOR_GET("interface/theme/base_color"); float contrast = EDITOR_GET("interface/theme/contrast"); + float icon_saturation = EDITOR_GET("interface/theme/icon_saturation"); float relationship_line_opacity = EDITOR_GET("interface/theme/relationship_line_opacity"); String preset = EDITOR_GET("interface/theme/preset"); @@ -393,6 +402,9 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { const Color highlight_color = Color(mono_color.r, mono_color.g, mono_color.b, 0.2); + float prev_icon_saturation = theme->has_color("icon_saturation", "Editor") ? theme->get_color("icon_saturation", "Editor").r : 1.0; + + theme->set_color("icon_saturation", "Editor", Color(icon_saturation, icon_saturation, icon_saturation)); //can't save single float in theme, so using color theme->set_color("accent_color", "Editor", accent_color); theme->set_color("highlight_color", "Editor", highlight_color); theme->set_color("base_color", "Editor", base_color); @@ -444,13 +456,13 @@ Ref<Theme> create_editor_theme(const Ref<Theme> p_theme) { //Register icons + font // the resolution and the icon color (dark_theme bool) has not changed, so we do not regenerate the icons - if (p_theme != nullptr && fabs(p_theme->get_constant("scale", "Editor") - EDSCALE) < 0.00001 && (bool)p_theme->get_constant("dark_theme", "Editor") == dark_theme) { + if (p_theme != nullptr && fabs(p_theme->get_constant("scale", "Editor") - EDSCALE) < 0.00001 && (bool)p_theme->get_constant("dark_theme", "Editor") == dark_theme && prev_icon_saturation == icon_saturation) { // register already generated icons for (int i = 0; i < editor_icons_count; i++) { theme->set_icon(editor_icons_names[i], "EditorIcons", p_theme->get_icon(editor_icons_names[i], "EditorIcons")); } } else { - editor_register_and_generate_icons(theme, dark_theme, thumb_size); + editor_register_and_generate_icons(theme, dark_theme, thumb_size, false, icon_saturation); } // thumbnail size has changed, so we regenerate the medium sizes if (p_theme != nullptr && fabs((double)p_theme->get_constant("thumb_size", "Editor") - thumb_size) > 0.00001) { diff --git a/editor/plugins/sprite_2d_editor_plugin.cpp b/editor/plugins/sprite_2d_editor_plugin.cpp index 03ddaa2c74..4949d2b9b7 100644 --- a/editor/plugins/sprite_2d_editor_plugin.cpp +++ b/editor/plugins/sprite_2d_editor_plugin.cpp @@ -173,6 +173,11 @@ void Sprite2DEditor::_update_mesh_data() { Ref<Image> image = texture->get_data(); ERR_FAIL_COND(image.is_null()); + + if (image->is_compressed()) { + image->decompress(); + } + Rect2 rect; if (node->is_region()) { rect = node->get_region_rect(); diff --git a/editor/scene_tree_dock.cpp b/editor/scene_tree_dock.cpp index 11f44720da..c1edeeeb0e 100644 --- a/editor/scene_tree_dock.cpp +++ b/editor/scene_tree_dock.cpp @@ -87,6 +87,12 @@ void SceneTreeDock::_unhandled_key_input(Ref<InputEvent> p_event) { _tool_selected(TOOL_INSTANCE); } else if (ED_IS_SHORTCUT("scene_tree/expand_collapse_all", p_event)) { _tool_selected(TOOL_EXPAND_COLLAPSE); + } else if (ED_IS_SHORTCUT("scene_tree/cut_node", p_event)) { + _tool_selected(TOOL_CUT); + } else if (ED_IS_SHORTCUT("scene_tree/copy_node", p_event)) { + _tool_selected(TOOL_COPY); + } else if (ED_IS_SHORTCUT("scene_tree/paste_node", p_event)) { + _tool_selected(TOOL_PASTE); } else if (ED_IS_SHORTCUT("scene_tree/change_node_type", p_event)) { _tool_selected(TOOL_REPLACE); } else if (ED_IS_SHORTCUT("scene_tree/duplicate", p_event)) { @@ -397,6 +403,114 @@ void SceneTreeDock::_tool_selected(int p_tool, bool p_confirm_override) { tree->ensure_cursor_is_visible(); } break; + case TOOL_CUT: + case TOOL_COPY: { + if (!edited_scene || !_validate_no_foreign()) { + break; + } + + List<Node *> selection = editor_selection->get_selected_node_list(); + if (selection.size() == 0) { + break; + } + + if (!node_clipboard.is_empty()) { + _clear_clipboard(); + } + clipboard_source_scene = editor->get_edited_scene()->get_filename(); + + selection.sort_custom<Node::Comparator>(); + + for (List<Node *>::Element *E = selection.front(); E; E = E->next()) { + Node *node = E->get(); + Map<const Node *, Node *> duplimap; + Node *dup = node->duplicate_from_editor(duplimap); + + ERR_CONTINUE(!dup); + + node_clipboard.push_back(dup); + } + + if (p_tool == TOOL_CUT) { + _delete_confirm(true); + } + } break; + case TOOL_PASTE: { + if (node_clipboard.is_empty() || !edited_scene) { + break; + } + + bool has_cycle = false; + if (edited_scene->get_filename() != String()) { + for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) { + if (edited_scene->get_filename() == E->get()->get_filename()) { + has_cycle = true; + break; + } + } + } + + if (has_cycle) { + current_option = -1; + accept->set_text(TTR("Can't paste root node into the same scene.")); + accept->popup_centered(); + break; + } + + Node *paste_parent = edited_scene; + List<Node *> selection = editor_selection->get_selected_node_list(); + if (selection.size() > 0) { + paste_parent = selection.back()->get(); + } + + Node *owner = paste_parent->get_owner(); + if (!owner) { + owner = paste_parent; + } + + editor_data->get_undo_redo().create_action(TTR("Paste Node(s)")); + editor_data->get_undo_redo().add_do_method(editor_selection, "clear"); + + Map<RES, RES> resource_remap; + String target_scene = editor->get_edited_scene()->get_filename(); + if (target_scene != clipboard_source_scene) { + if (!clipboard_resource_remap.has(target_scene)) { + Map<RES, RES> remap; + for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) { + _create_remap_for_node(E->get(), remap); + } + clipboard_resource_remap[target_scene] = remap; + } + resource_remap = clipboard_resource_remap[target_scene]; + } + + for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) { + Node *node = E->get(); + Map<const Node *, Node *> duplimap; + + Node *dup = node->duplicate_from_editor(duplimap, resource_remap); + + ERR_CONTINUE(!dup); + + editor_data->get_undo_redo().add_do_method(paste_parent, "add_child", dup); + + for (Map<const Node *, Node *>::Element *E2 = duplimap.front(); E2; E2 = E2->next()) { + Node *d = E2->value(); + editor_data->get_undo_redo().add_do_method(d, "set_owner", owner); + } + + editor_data->get_undo_redo().add_do_method(dup, "set_owner", owner); + editor_data->get_undo_redo().add_do_method(editor_selection, "add_node", dup); + editor_data->get_undo_redo().add_undo_method(paste_parent, "remove_child", dup); + editor_data->get_undo_redo().add_do_reference(dup); + + if (node_clipboard.size() == 1) { + editor_data->get_undo_redo().add_do_method(editor, "push_item", dup); + } + } + + editor_data->get_undo_redo().commit_action(); + } break; case TOOL_REPLACE: { if (!profile_allow_editing) { break; @@ -1795,7 +1909,7 @@ void SceneTreeDock::_toggle_editable_children(Node *p_node) { } } -void SceneTreeDock::_delete_confirm() { +void SceneTreeDock::_delete_confirm(bool p_cut) { List<Node *> remove_list = editor_selection->get_selected_node_list(); if (remove_list.is_empty()) { @@ -1804,7 +1918,11 @@ void SceneTreeDock::_delete_confirm() { editor->get_editor_plugins_over()->make_visible(false); - editor_data->get_undo_redo().create_action(TTR("Remove Node(s)")); + if (p_cut) { + editor_data->get_undo_redo().create_action(TTR("Cut Node(s)")); + } else { + editor_data->get_undo_redo().create_action(TTR("Remove Node(s)")); + } bool entire_scene = false; @@ -2444,6 +2562,13 @@ void SceneTreeDock::_tree_rmb(const Vector2 &p_menu_pos) { } if (profile_allow_script_editing) { + menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/cut_node"), TOOL_CUT); + menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/copy_node"), TOOL_COPY); + if (selection.size() == 1 && !node_clipboard.is_empty()) { + menu->add_shortcut(ED_GET_SHORTCUT("scene_tree/paste_node"), TOOL_PASTE); + } + menu->add_separator(); + bool add_separator = false; if (full_selection.size() == 1) { @@ -2775,6 +2900,62 @@ void SceneTreeDock::_feature_profile_changed() { _update_script_button(); } +void SceneTreeDock::_clear_clipboard() { + for (List<Node *>::Element *E = node_clipboard.front(); E; E = E->next()) { + memdelete(E->get()); + } + node_clipboard.clear(); + clipboard_resource_remap.clear(); +} + +void SceneTreeDock::_create_remap_for_node(Node *p_node, Map<RES, RES> &r_remap) { + List<PropertyInfo> props; + p_node->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + Variant v = p_node->get(E->get().name); + if (v.is_ref()) { + RES res = v; + if (res.is_valid()) { + if (res->get_path() == "" && !r_remap.has(res)) { + _create_remap_for_resource(res, r_remap); + } + } + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + _create_remap_for_node(p_node->get_child(i), r_remap); + } +} + +void SceneTreeDock::_create_remap_for_resource(RES p_resource, Map<RES, RES> &r_remap) { + r_remap[p_resource] = p_resource->duplicate(); + + List<PropertyInfo> props; + p_resource->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + Variant v = p_resource->get(E->get().name); + if (v.is_ref()) { + RES res = v; + if (res.is_valid()) { + if (res->get_path() == "" && !r_remap.has(res)) { + _create_remap_for_resource(res, r_remap); + } + } + } + } +} + void SceneTreeDock::_bind_methods() { ClassDB::bind_method(D_METHOD("_set_owners"), &SceneTreeDock::_set_owners); ClassDB::bind_method(D_METHOD("_unhandled_key_input"), &SceneTreeDock::_unhandled_key_input); @@ -2806,6 +2987,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel ED_SHORTCUT("scene_tree/add_child_node", TTR("Add Child Node"), KEY_MASK_CMD | KEY_A); ED_SHORTCUT("scene_tree/instance_scene", TTR("Instance Child Scene")); ED_SHORTCUT("scene_tree/expand_collapse_all", TTR("Expand/Collapse All")); + ED_SHORTCUT("scene_tree/cut_node", TTR("Cut"), KEY_MASK_CMD | KEY_X); + ED_SHORTCUT("scene_tree/copy_node", TTR("Copy"), KEY_MASK_CMD | KEY_C); + ED_SHORTCUT("scene_tree/paste_node", TTR("Paste"), KEY_MASK_CMD | KEY_V); ED_SHORTCUT("scene_tree/change_node_type", TTR("Change Type")); ED_SHORTCUT("scene_tree/attach_script", TTR("Attach Script")); ED_SHORTCUT("scene_tree/extend_script", TTR("Extend Script")); @@ -2818,7 +3002,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel ED_SHORTCUT("scene_tree/make_root", TTR("Make Scene Root")); ED_SHORTCUT("scene_tree/merge_from_scene", TTR("Merge From Scene")); ED_SHORTCUT("scene_tree/save_branch_as_scene", TTR("Save Branch as Scene")); - ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KEY_MASK_CMD | KEY_C); + ED_SHORTCUT("scene_tree/copy_node_path", TTR("Copy Node Path"), KEY_MASK_CMD | KEY_MASK_SHIFT | KEY_C); ED_SHORTCUT("scene_tree/delete_no_confirm", TTR("Delete (No Confirm)"), KEY_MASK_SHIFT | KEY_DELETE); ED_SHORTCUT("scene_tree/delete", TTR("Delete"), KEY_DELETE); @@ -2936,7 +3120,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel delete_dialog = memnew(ConfirmationDialog); add_child(delete_dialog); - delete_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_delete_confirm)); + delete_dialog->connect("confirmed", callable_mp(this, &SceneTreeDock::_delete_confirm), varray(false)); editable_instance_remove_dialog = memnew(ConfirmationDialog); add_child(editable_instance_remove_dialog); @@ -2981,3 +3165,9 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel EDITOR_DEF("interface/editors/derive_script_globals_by_name", true); EDITOR_DEF("_use_favorites_root_selection", false); } + +SceneTreeDock::~SceneTreeDock() { + if (!node_clipboard.is_empty()) { + _clear_clipboard(); + } +} diff --git a/editor/scene_tree_dock.h b/editor/scene_tree_dock.h index 4f8d85f07c..a042188be6 100644 --- a/editor/scene_tree_dock.h +++ b/editor/scene_tree_dock.h @@ -58,6 +58,9 @@ class SceneTreeDock : public VBoxContainer { TOOL_NEW, TOOL_INSTANCE, TOOL_EXPAND_COLLAPSE, + TOOL_CUT, + TOOL_COPY, + TOOL_PASTE, TOOL_RENAME, TOOL_BATCH_RENAME, TOOL_REPLACE, @@ -126,6 +129,10 @@ class SceneTreeDock : public VBoxContainer { EditorData *editor_data; EditorSelection *editor_selection; + List<Node *> node_clipboard; + String clipboard_source_scene; + HashMap<String, Map<RES, RES>> clipboard_resource_remap; + ScriptCreateDialog *script_create_dialog; AcceptDialog *accept; ConfirmationDialog *delete_dialog; @@ -183,7 +190,7 @@ class SceneTreeDock : public VBoxContainer { void _script_created(Ref<Script> p_script); void _script_creation_closed(); - void _delete_confirm(); + void _delete_confirm(bool p_cut = false); void _toggle_editable_children_from_selection(); void _toggle_editable_children(Node *p_node); @@ -230,6 +237,10 @@ class SceneTreeDock : public VBoxContainer { void _feature_profile_changed(); + void _clear_clipboard(); + void _create_remap_for_node(Node *p_node, Map<RES, RES> &r_remap); + void _create_remap_for_resource(RES p_resource, Map<RES, RES> &r_remap); + bool profile_allow_editing; bool profile_allow_script_editing; @@ -267,6 +278,7 @@ public: ScriptCreateDialog *get_script_create_dialog() { return script_create_dialog; } SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSelection *p_editor_selection, EditorData &p_editor_data); + ~SceneTreeDock(); }; #endif // SCENE_TREE_DOCK_H diff --git a/scene/main/node.cpp b/scene/main/node.cpp index 9ac3b4a691..e109240f59 100644 --- a/scene/main/node.cpp +++ b/scene/main/node.cpp @@ -2138,8 +2138,17 @@ Node *Node::duplicate(int p_flags) const { #ifdef TOOLS_ENABLED Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const { + return duplicate_from_editor(r_duplimap, Map<RES, RES>()); +} + +Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const { Node *dupe = _duplicate(DUPLICATE_SIGNALS | DUPLICATE_GROUPS | DUPLICATE_SCRIPTS | DUPLICATE_USE_INSTANCING | DUPLICATE_FROM_EDITOR, &r_duplimap); + // This is used by SceneTreeDock's paste functionality. When pasting to foreign scene, resources are duplicated. + if (!p_resource_remap.is_empty()) { + remap_node_resources(dupe, p_resource_remap); + } + // Duplication of signals must happen after all the node descendants have been copied, // because re-targeting of connections from some descendant to another is not possible // if the emitter node comes later in tree order than the receiver @@ -2147,6 +2156,54 @@ Node *Node::duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const { return dupe; } + +void Node::remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const { + List<PropertyInfo> props; + p_node->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + Variant v = p_node->get(E->get().name); + if (v.is_ref()) { + RES res = v; + if (res.is_valid()) { + if (p_resource_remap.has(res)) { + p_node->set(E->get().name, p_resource_remap[res]); + remap_nested_resources(res, p_resource_remap); + } + } + } + } + + for (int i = 0; i < p_node->get_child_count(); i++) { + remap_node_resources(p_node->get_child(i), p_resource_remap); + } +} + +void Node::remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const { + List<PropertyInfo> props; + p_resource->get_property_list(&props); + + for (List<PropertyInfo>::Element *E = props.front(); E; E = E->next()) { + if (!(E->get().usage & PROPERTY_USAGE_STORAGE)) { + continue; + } + + Variant v = p_resource->get(E->get().name); + if (v.is_ref()) { + RES res = v; + if (res.is_valid()) { + if (p_resource_remap.has(res)) { + p_resource->set(E->get().name, p_resource_remap[res]); + remap_nested_resources(res, p_resource_remap); + } + } + } + } +} #endif void Node::_duplicate_and_reown(Node *p_new_parent, const Map<Node *, Node *> &p_reown_map) const { diff --git a/scene/main/node.h b/scene/main/node.h index 5253ed2e45..66104b5cf5 100644 --- a/scene/main/node.h +++ b/scene/main/node.h @@ -362,6 +362,9 @@ public: Node *duplicate_and_reown(const Map<Node *, Node *> &p_reown_map) const; #ifdef TOOLS_ENABLED Node *duplicate_from_editor(Map<const Node *, Node *> &r_duplimap) const; + Node *duplicate_from_editor(Map<const Node *, Node *> &r_duplimap, const Map<RES, RES> &p_resource_remap) const; + void remap_node_resources(Node *p_node, const Map<RES, RES> &p_resource_remap) const; + void remap_nested_resources(RES p_resource, const Map<RES, RES> &p_resource_remap) const; #endif // used by editors, to save what has changed only diff --git a/servers/physics_3d/collision_solver_3d_sat.cpp b/servers/physics_3d/collision_solver_3d_sat.cpp index 2a31cf1c22..f507cacdc3 100644 --- a/servers/physics_3d/collision_solver_3d_sat.cpp +++ b/servers/physics_3d/collision_solver_3d_sat.cpp @@ -35,7 +35,7 @@ #define fallback_collision_solver gjk_epa_calculate_penetration -// Cylinder SAT analytic methods for Cylinder-trimesh and Cylinder-box are based on ODE colliders. +// Cylinder SAT analytic methods and face-circle contact points for cylinder-trimesh and cylinder-box collision are based on ODE colliders. /* * Cylinder-trimesh and Cylinder-box colliders by Alen Ladavac @@ -119,28 +119,9 @@ static void _generate_contacts_point_circle(const Vector3 *p_points_A, int p_poi ERR_FAIL_COND(p_point_count_B != 3); #endif - const Vector3 &point_A = p_points_A[0]; - - const Vector3 &circle_B_pos = p_points_B[0]; - Vector3 circle_B_line_1 = p_points_B[1] - circle_B_pos; - Vector3 circle_B_line_2 = p_points_B[2] - circle_B_pos; - - real_t circle_B_radius = circle_B_line_1.length(); - Vector3 circle_B_normal = circle_B_line_1.cross(circle_B_line_2).normalized(); - - // Project point onto Circle B plane. - Plane circle_plane(circle_B_pos, circle_B_normal); - Vector3 proj_point_A = circle_plane.project(point_A); - - // Clip point. - Vector3 delta_point_1 = proj_point_A - circle_B_pos; - real_t dist_point_1 = delta_point_1.length_squared(); - if (!Math::is_zero_approx(dist_point_1)) { - dist_point_1 = Math::sqrt(dist_point_1); - proj_point_A = circle_B_pos + delta_point_1 * MIN(dist_point_1, circle_B_radius) / dist_point_1; - } + Vector3 closest_B = Plane(p_points_B[0], p_points_B[1], p_points_B[2]).project(*p_points_A); - p_callback->call(point_A, proj_point_A); + p_callback->call(*p_points_A, closest_B); } static void _generate_contacts_edge_edge(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { @@ -189,6 +170,104 @@ static void _generate_contacts_edge_edge(const Vector3 *p_points_A, int p_point_ p_callback->call(closest_A, closest_B); } +static void _generate_contacts_edge_circle(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { +#ifdef DEBUG_ENABLED + ERR_FAIL_COND(p_point_count_A != 2); + ERR_FAIL_COND(p_point_count_B != 3); +#endif + + const Vector3 &circle_B_pos = p_points_B[0]; + Vector3 circle_B_line_1 = p_points_B[1] - circle_B_pos; + Vector3 circle_B_line_2 = p_points_B[2] - circle_B_pos; + + real_t circle_B_radius = circle_B_line_1.length(); + Vector3 circle_B_normal = circle_B_line_1.cross(circle_B_line_2).normalized(); + + Plane circle_plane(circle_B_pos, circle_B_normal); + + static const int max_clip = 2; + Vector3 contact_points[max_clip]; + int num_points = 0; + + // Project edge point in circle plane. + const Vector3 &edge_A_1 = p_points_A[0]; + Vector3 proj_point_1 = circle_plane.project(edge_A_1); + + Vector3 dist_vec = proj_point_1 - circle_B_pos; + real_t dist_sq = dist_vec.length_squared(); + + // Point 1 is inside disk, add as contact point. + if (dist_sq <= circle_B_radius * circle_B_radius) { + contact_points[num_points] = edge_A_1; + ++num_points; + } + + const Vector3 &edge_A_2 = p_points_A[1]; + Vector3 proj_point_2 = circle_plane.project(edge_A_2); + + Vector3 dist_vec_2 = proj_point_2 - circle_B_pos; + real_t dist_sq_2 = dist_vec_2.length_squared(); + + // Point 2 is inside disk, add as contact point. + if (dist_sq_2 <= circle_B_radius * circle_B_radius) { + contact_points[num_points] = edge_A_2; + ++num_points; + } + + if (num_points < 2) { + Vector3 line_vec = proj_point_2 - proj_point_1; + real_t line_length_sq = line_vec.length_squared(); + + // Create a quadratic formula of the form ax^2 + bx + c = 0 + real_t a, b, c; + + a = line_length_sq; + b = 2.0 * dist_vec.dot(line_vec); + c = dist_sq - circle_B_radius * circle_B_radius; + + // Solve for t. + real_t sqrtterm = b * b - 4.0 * a * c; + + // If the term we intend to square root is less than 0 then the answer won't be real, + // so the line doesn't intersect. + if (sqrtterm >= 0) { + sqrtterm = Math::sqrt(sqrtterm); + + Vector3 edge_dir = edge_A_2 - edge_A_1; + + real_t fraction_1 = (-b - sqrtterm) / (2.0 * a); + if ((fraction_1 > 0.0) && (fraction_1 < 1.0)) { + Vector3 face_point_1 = edge_A_1 + fraction_1 * edge_dir; + ERR_FAIL_COND(num_points >= max_clip); + contact_points[num_points] = face_point_1; + ++num_points; + } + + real_t fraction_2 = (-b + sqrtterm) / (2.0 * a); + if ((fraction_2 > 0.0) && (fraction_2 < 1.0) && !Math::is_equal_approx(fraction_1, fraction_2)) { + Vector3 face_point_2 = edge_A_1 + fraction_2 * edge_dir; + ERR_FAIL_COND(num_points >= max_clip); + contact_points[num_points] = face_point_2; + ++num_points; + } + } + } + + // Generate contact points. + for (int i = 0; i < num_points; i++) { + const Vector3 &contact_point_A = contact_points[i]; + + real_t d = circle_plane.distance_to(contact_point_A); + Vector3 closest_B = contact_point_A - circle_plane.normal * d; + + if (p_callback->normal.dot(contact_point_A) >= p_callback->normal.dot(closest_B)) { + continue; + } + + p_callback->call(contact_point_A, closest_B); + } +} + static void _generate_contacts_face_face(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { #ifdef DEBUG_ENABLED ERR_FAIL_COND(p_point_count_A < 2); @@ -280,7 +359,7 @@ static void _generate_contacts_face_face(const Vector3 *p_points_A, int p_point_ static void _generate_contacts_face_circle(const Vector3 *p_points_A, int p_point_count_A, const Vector3 *p_points_B, int p_point_count_B, _CollectorCallback *p_callback) { #ifdef DEBUG_ENABLED - ERR_FAIL_COND(p_point_count_A < 2); + ERR_FAIL_COND(p_point_count_A < 3); ERR_FAIL_COND(p_point_count_B != 3); #endif @@ -288,150 +367,60 @@ static void _generate_contacts_face_circle(const Vector3 *p_points_A, int p_poin Vector3 circle_B_line_1 = p_points_B[1] - circle_B_pos; Vector3 circle_B_line_2 = p_points_B[2] - circle_B_pos; - real_t circle_B_radius = circle_B_line_1.length(); + // Clip face with circle segments. + static const int circle_segments = 8; + Vector3 circle_points[circle_segments]; + + real_t angle_delta = 2.0 * Math_PI / circle_segments; + + for (int i = 0; i < circle_segments; ++i) { + Vector3 point_pos = circle_B_pos; + point_pos += circle_B_line_1 * Math::cos(i * angle_delta); + point_pos += circle_B_line_2 * Math::sin(i * angle_delta); + circle_points[i] = point_pos; + } + + _generate_contacts_face_face(p_points_A, p_point_count_A, circle_points, circle_segments, p_callback); + + // Clip face with circle plane. Vector3 circle_B_normal = circle_B_line_1.cross(circle_B_line_2).normalized(); Plane circle_plane(circle_B_pos, circle_B_normal); - bool edge = (p_point_count_A == 2); - static const int max_clip = 32; Vector3 contact_points[max_clip]; int num_points = 0; - // Clip edges with circle. for (int i = 0; i < p_point_count_A; i++) { int i_n = (i + 1) % p_point_count_A; - // Project edge point in circle plane. - const Vector3 &edge_A_1 = p_points_A[i]; - Vector3 proj_point_1 = circle_plane.project(edge_A_1); + const Vector3 &edge0_A = p_points_A[i]; + const Vector3 &edge1_A = p_points_A[i_n]; - Vector3 dist_vec = proj_point_1 - circle_B_pos; - real_t dist_sq = dist_vec.length_squared(); + real_t dist0 = circle_plane.distance_to(edge0_A); + real_t dist1 = circle_plane.distance_to(edge1_A); - // Point 1 is inside disk, add as contact point. - if (dist_sq <= circle_B_radius * circle_B_radius) { - //p_callback->call(edge_A_1, proj_point_1); + // First point in front of plane, generate contact point. + if (dist0 * circle_plane.d >= 0) { ERR_FAIL_COND(num_points >= max_clip); - contact_points[num_points] = edge_A_1; + contact_points[num_points] = edge0_A; ++num_points; } - // No need to test point 2 now, as it will be part of the next edge. - - if (edge && i > 0) { - // Done with testing the only two points. - break; - } - // Project edge point in circle plane. - const Vector3 &edge_A_2 = p_points_A[i_n]; - Vector3 proj_point_2 = circle_plane.project(edge_A_2); + // Points on different sides, generate contact point. + if (dist0 * dist1 < 0) { + // calculate intersection + Vector3 rel = edge1_A - edge0_A; + real_t den = circle_plane.normal.dot(rel); + real_t dist = -(circle_plane.normal.dot(edge0_A) - circle_plane.d) / den; + Vector3 inters = edge0_A + rel * dist; - Vector3 line_vec = proj_point_2 - proj_point_1; - real_t line_length_sq = line_vec.length_squared(); - - // Create a quadratic formula of the form ax^2 + bx + c = 0 - real_t a, b, c; - - a = line_length_sq; - b = 2.0 * dist_vec.dot(line_vec); - c = dist_sq - circle_B_radius * circle_B_radius; - - // Solve for t. - real_t sqrtterm = b * b - 4.0 * a * c; - - // If the term we intend to square root is less than 0 then the answer won't be real, - // so the line doesn't intersect. - if (sqrtterm < 0) { - continue; - } - - sqrtterm = Math::sqrt(sqrtterm); - - Vector3 edge_dir = edge_A_2 - edge_A_1; - - real_t fraction_1 = (-b - sqrtterm) / (2.0 * a); - if ((fraction_1 > 0.0) && (fraction_1 < 1.0)) { - //Vector3 intersection_1 = proj_point_1 + fraction_1 * line_vec; - Vector3 face_point_1 = edge_A_1 + fraction_1 * edge_dir; - //p_callback->call(face_point_1, intersection_1); - ERR_FAIL_COND(num_points >= max_clip); - contact_points[num_points] = face_point_1; - ++num_points; - } - - real_t fraction_2 = (-b + sqrtterm) / (2.0 * a); - if ((fraction_2 > 0.0) && (fraction_2 < 1.0) && !Math::is_equal_approx(fraction_1, fraction_2)) { - //Vector3 intersection_2 = proj_point_1 + fraction_2 * line_vec; - Vector3 face_point_2 = edge_A_1 + fraction_2 * edge_dir; - //p_callback->call(face_point_2, intersection_2); ERR_FAIL_COND(num_points >= max_clip); - contact_points[num_points] = face_point_2; + contact_points[num_points] = inters; ++num_points; } } - // In case of a face, add extra contact points for proper support. - if (!edge) { - Plane plane_A(p_points_A[0], p_points_A[1], p_points_A[2]); - - if (num_points < 3) { - if (num_points == 0) { - // Use 3 arbitrary equidistant points from the circle. - for (int i = 0; i < 3; ++i) { - Vector3 circle_point = circle_B_pos; - circle_point += circle_B_line_1 * Math::cos(2.0 * Math_PI * i / 3.0); - circle_point += circle_B_line_2 * Math::sin(2.0 * Math_PI * i / 3.0); - - Vector3 face_point = plane_A.project(circle_point); - - contact_points[num_points] = face_point; - ++num_points; - } - } else if (num_points == 1) { - Vector3 line_center = circle_B_pos - contact_points[0]; - Vector3 line_tangent = line_center.cross(plane_A.normal); - - Vector3 dir = line_tangent.cross(plane_A.normal).normalized(); - if (line_center.dot(dir) > 0.0) { - // Use 2 equidistant points on the circle inside the face. - line_center.normalize(); - line_tangent.normalize(); - for (int i = 0; i < 2; ++i) { - Vector3 circle_point = circle_B_pos; - circle_point -= line_center * circle_B_radius * Math::cos(2.0 * Math_PI * (i + 1) / 3.0); - circle_point += line_tangent * circle_B_radius * Math::sin(2.0 * Math_PI * (i + 1) / 3.0); - - Vector3 face_point = plane_A.project(circle_point); - - contact_points[num_points] = face_point; - ++num_points; - } - } - // Otherwise the circle touches an edge from the outside, no extra contact point. - } else { // if (num_points == 2) - // Use equidistant 3rd point on the circle inside the face. - Vector3 contacts_line = contact_points[1] - contact_points[0]; - Vector3 dir = contacts_line.cross(plane_A.normal).normalized(); - - Vector3 circle_point = contact_points[0] + 0.5 * contacts_line; - Vector3 line_center = (circle_B_pos - circle_point); - - if (line_center.dot(dir) > 0.0) { - circle_point += dir * (line_center.length() + circle_B_radius); - } else { - circle_point += dir * (circle_B_radius - line_center.length()); - } - - Vector3 face_point = plane_A.project(circle_point); - - contact_points[num_points] = face_point; - ++num_points; - } - } - } - // Generate contact points. for (int i = 0; i < num_points; i++) { const Vector3 &contact_point_A = contact_points[i]; @@ -567,7 +556,7 @@ static void _generate_contacts_from_supports(const Vector3 *p_points_A, int p_po nullptr, _generate_contacts_edge_edge, _generate_contacts_face_face, - _generate_contacts_face_circle, + _generate_contacts_edge_circle, }, { nullptr, |