diff options
-rw-r--r-- | editor/plugins/sprite_2d_editor_plugin.cpp | 5 | ||||
-rw-r--r-- | editor/scene_tree_dock.cpp | 200 | ||||
-rw-r--r-- | editor/scene_tree_dock.h | 14 | ||||
-rw-r--r-- | editor/scene_tree_editor.cpp | 2 | ||||
-rw-r--r-- | modules/gridmap/grid_map_editor_plugin.cpp | 2 | ||||
-rw-r--r-- | scene/gui/line_edit.cpp | 6 | ||||
-rw-r--r-- | scene/gui/text_edit.cpp | 2 | ||||
-rw-r--r-- | scene/main/node.cpp | 57 | ||||
-rw-r--r-- | scene/main/node.h | 3 | ||||
-rw-r--r-- | scene/resources/default_theme/default_theme.cpp | 2 | ||||
-rw-r--r-- | servers/physics_3d/collision_solver_3d_sat.cpp | 279 |
11 files changed, 414 insertions, 158 deletions
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 ac1beb1c37..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); @@ -2841,7 +3025,7 @@ SceneTreeDock::SceneTreeDock(EditorNode *p_editor, Node *p_scene_root, EditorSel filter->set_h_size_flags(SIZE_EXPAND_FILL); filter->set_placeholder(TTR("Filter nodes")); filter_hbc->add_child(filter); - filter->add_theme_constant_override("minimum_spaces", 0); + filter->add_theme_constant_override("minimum_character_width", 0); filter->connect("text_changed", callable_mp(this, &SceneTreeDock::_filter_changed)); button_create_script = memnew(Button); @@ -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/editor/scene_tree_editor.cpp b/editor/scene_tree_editor.cpp index ce44a4bca1..2cd64a81dc 100644 --- a/editor/scene_tree_editor.cpp +++ b/editor/scene_tree_editor.cpp @@ -1260,7 +1260,7 @@ SceneTreeDialog::SceneTreeDialog() { filter = memnew(LineEdit); filter->set_h_size_flags(Control::SIZE_EXPAND_FILL); filter->set_placeholder(TTR("Filter nodes")); - filter->add_theme_constant_override("minimum_spaces", 0); + filter->add_theme_constant_override("minimum_character_width", 0); filter->connect("text_changed", callable_mp(this, &SceneTreeDialog::_filter_changed)); vbc->add_child(filter); diff --git a/modules/gridmap/grid_map_editor_plugin.cpp b/modules/gridmap/grid_map_editor_plugin.cpp index 565830c16f..da9cdb9bc5 100644 --- a/modules/gridmap/grid_map_editor_plugin.cpp +++ b/modules/gridmap/grid_map_editor_plugin.cpp @@ -1172,7 +1172,7 @@ GridMapEditor::GridMapEditor(EditorNode *p_editor) { floor->set_min(-32767); floor->set_max(32767); floor->set_step(1); - floor->get_line_edit()->add_theme_constant_override("minimum_spaces", 16); + floor->get_line_edit()->add_theme_constant_override("minimum_character_width", 16); spatial_editor_hb->add_child(floor); floor->connect("value_changed", callable_mp(this, &GridMapEditor::_floor_changed)); diff --git a/scene/gui/line_edit.cpp b/scene/gui/line_edit.cpp index 14c84eb256..654507b933 100644 --- a/scene/gui/line_edit.cpp +++ b/scene/gui/line_edit.cpp @@ -1568,12 +1568,12 @@ Size2 LineEdit::get_minimum_size() const { Size2 min_size; // Minimum size of text. - int space_size = font->get_char_size(' ', 0, font_size).x; - min_size.width = get_theme_constant("minimum_spaces") * space_size; + int em_space_size = font->get_char_size('M', 0, font_size).x; + min_size.width = get_theme_constant("minimum_character_width'") * em_space_size; if (expand_to_text_length) { // Add a space because some fonts are too exact, and because cursor needs a bit more when at the end. - min_size.width = MAX(min_size.width, full_width + space_size); + min_size.width = MAX(min_size.width, full_width + em_space_size); } min_size.height = MAX(TS->shaped_text_get_size(text_rid).y + font->get_spacing(Font::SPACING_TOP) + font->get_spacing(Font::SPACING_BOTTOM), font->get_height(font_size)); diff --git a/scene/gui/text_edit.cpp b/scene/gui/text_edit.cpp index 85587c6f5f..3306a11dd0 100644 --- a/scene/gui/text_edit.cpp +++ b/scene/gui/text_edit.cpp @@ -1380,7 +1380,7 @@ void TextEdit::_notification(int p_what) { l_caret.size.y = h; } l_caret.position += Vector2(char_margin + ofs_x, ofs_y); - l_caret.size.x = cache.font->get_char_size('m', 0, cache.font_size).x; + l_caret.size.x = cache.font->get_char_size('M', 0, cache.font_size).x; draw_rect(l_caret, cache.caret_color, false); } 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/scene/resources/default_theme/default_theme.cpp b/scene/resources/default_theme/default_theme.cpp index 31bc00e528..e66de82ed9 100644 --- a/scene/resources/default_theme/default_theme.cpp +++ b/scene/resources/default_theme/default_theme.cpp @@ -394,7 +394,7 @@ void fill_default_theme(Ref<Theme> &theme, const Ref<Font> &default_font, const theme->set_color("clear_button_color", "LineEdit", control_font_color); theme->set_color("clear_button_color_pressed", "LineEdit", control_font_pressed_color); - theme->set_constant("minimum_spaces", "LineEdit", 12 * scale); + theme->set_constant("minimum_character_width", "LineEdit", 4); theme->set_icon("clear", "LineEdit", make_icon(line_edit_clear_png)); 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, |