summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/io/image.cpp22
-rw-r--r--core/io/image.h2
-rw-r--r--editor/editor_settings.cpp2
-rw-r--r--editor/editor_themes.cpp22
-rw-r--r--editor/plugins/sprite_2d_editor_plugin.cpp5
-rw-r--r--editor/scene_tree_dock.cpp198
-rw-r--r--editor/scene_tree_dock.h14
-rw-r--r--scene/main/node.cpp57
-rw-r--r--scene/main/node.h3
-rw-r--r--servers/physics_3d/collision_solver_3d_sat.cpp279
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,