diff options
-rw-r--r-- | editor/plugins/node_3d_editor_plugin.cpp | 13 | ||||
-rw-r--r-- | editor/plugins/node_3d_editor_plugin.h | 2 | ||||
-rw-r--r-- | editor/plugins/path_3d_editor_plugin.cpp | 209 | ||||
-rw-r--r-- | editor/plugins/path_3d_editor_plugin.h | 16 |
4 files changed, 196 insertions, 44 deletions
diff --git a/editor/plugins/node_3d_editor_plugin.cpp b/editor/plugins/node_3d_editor_plugin.cpp index b9b342245f..15a9cfad22 100644 --- a/editor/plugins/node_3d_editor_plugin.cpp +++ b/editor/plugins/node_3d_editor_plugin.cpp @@ -646,6 +646,18 @@ void Node3DEditorViewport::cancel_transform() { continue; } + if (se && se->gizmo.is_valid()) { + Vector<int> ids; + Vector<Transform3D> restore; + + for (const KeyValue<int, Transform3D> &GE : se->subgizmos) { + ids.push_back(GE.key); + restore.push_back(GE.value); + } + + se->gizmo->commit_subgizmos(ids, restore, true); + } + sp->set_global_transform(se->original); } @@ -8071,6 +8083,7 @@ void Node3DEditor::_bind_methods() { ClassDB::bind_method("_refresh_menu_icons", &Node3DEditor::_refresh_menu_icons); ClassDB::bind_method("update_all_gizmos", &Node3DEditor::update_all_gizmos); + ClassDB::bind_method("update_transform_gizmo", &Node3DEditor::update_transform_gizmo); ADD_SIGNAL(MethodInfo("transform_key_request")); ADD_SIGNAL(MethodInfo("item_lock_status_changed")); diff --git a/editor/plugins/node_3d_editor_plugin.h b/editor/plugins/node_3d_editor_plugin.h index ed42e8e5ab..61b1d38cfc 100644 --- a/editor/plugins/node_3d_editor_plugin.h +++ b/editor/plugins/node_3d_editor_plugin.h @@ -509,7 +509,7 @@ public: RID sbox_instance_xray; RID sbox_instance_xray_offset; Ref<EditorNode3DGizmo> gizmo; - HashMap<int, Transform3D> subgizmos; // map ID -> initial transform + HashMap<int, Transform3D> subgizmos; // Key: Subgizmo ID, Value: Initial subgizmo transform. Node3DEditorSelectedItem() { sp = nullptr; diff --git a/editor/plugins/path_3d_editor_plugin.cpp b/editor/plugins/path_3d_editor_plugin.cpp index 6fe1949382..ffdc06ceee 100644 --- a/editor/plugins/path_3d_editor_plugin.cpp +++ b/editor/plugins/path_3d_editor_plugin.cpp @@ -200,22 +200,6 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - // Primary handles: position. - if (!p_secondary) { - // Special cas for primary handle, the handle id equals control point id. - const int idx = p_id; - if (p_cancel) { - c->set_point_position(idx, p_restore); - return; - } - ur->create_action(TTR("Set Curve Point Position")); - ur->add_do_method(c.ptr(), "set_point_position", idx, c->get_point_position(idx)); - ur->add_undo_method(c.ptr(), "set_point_position", idx, p_restore); - ur->commit_action(); - - return; - } - // Secondary handles: in, out, tilt. const HandleInfo info = _secondary_handles_info[p_id]; const int idx = info.point_idx; @@ -235,6 +219,7 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res ur->add_do_method(c.ptr(), "set_point_in", idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -c->get_point_out(idx) : (-c->get_point_out(idx).normalized() * orig_in_length)); ur->add_undo_method(c.ptr(), "set_point_in", idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -static_cast<Vector3>(p_restore) : (-static_cast<Vector3>(p_restore).normalized() * orig_in_length)); } + ur->commit_action(); break; } @@ -252,6 +237,7 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res ur->add_do_method(c.ptr(), "set_point_out", idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -c->get_point_in(idx) : (-c->get_point_in(idx).normalized() * orig_out_length)); ur->add_undo_method(c.ptr(), "set_point_out", idx, Path3DEditorPlugin::singleton->mirror_length_enabled() ? -static_cast<Vector3>(p_restore) : (-static_cast<Vector3>(p_restore).normalized() * orig_out_length)); } + ur->commit_action(); break; } @@ -263,6 +249,7 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res ur->create_action(TTR("Set Curve Point Tilt")); ur->add_do_method(c.ptr(), "set_point_tilt", idx, c->get_point_tilt(idx)); ur->add_undo_method(c.ptr(), "set_point_tilt", idx, p_restore); + ur->commit_action(); break; } @@ -275,7 +262,7 @@ void Path3DGizmo::redraw() { Ref<StandardMaterial3D> path_material = gizmo_plugin->get_material("path_material", this); Ref<StandardMaterial3D> path_thin_material = gizmo_plugin->get_material("path_thin_material", this); Ref<StandardMaterial3D> path_tilt_material = gizmo_plugin->get_material("path_tilt_material", this); - Ref<StandardMaterial3D> handles_material = gizmo_plugin->get_material("handles"); + Ref<StandardMaterial3D> path_tilt_muted_material = gizmo_plugin->get_material("path_tilt_muted_material", this); Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles"); Ref<Curve3D> c = path->get_curve(); @@ -353,48 +340,57 @@ void Path3DGizmo::redraw() { if (Path3DEditorPlugin::singleton->get_edited_path() == path) { PackedVector3Array handle_lines; PackedVector3Array tilt_handle_lines; - PackedVector3Array primary_handle_points; PackedVector3Array secondary_handle_points; PackedInt32Array collected_secondary_handle_ids; // Avoid shadowing member on Node3DEditorGizmo. _secondary_handles_info.resize(c->get_point_count() * 3); for (int idx = 0; idx < c->get_point_count(); idx++) { - // Collect primary-handles. const Vector3 pos = c->get_point_position(idx); - primary_handle_points.append(pos); + bool is_current_point_selected = is_subgizmo_selected(idx); + bool is_previous_point_selected = is_subgizmo_selected(idx - 1); + bool is_following_point_selected = is_subgizmo_selected(idx + 1); HandleInfo info; info.point_idx = idx; // Collect in-handles except for the first point. - if (idx > 0) { - info.type = HandleType::HANDLE_TYPE_IN; - const int handle_idx = idx * 3 + 0; - collected_secondary_handle_ids.append(handle_idx); - _secondary_handles_info.write[handle_idx] = info; - + if (idx > 0 && (is_current_point_selected || is_previous_point_selected)) { const Vector3 in = c->get_point_in(idx); - secondary_handle_points.append(pos + in); - handle_lines.append(pos); - handle_lines.append(pos + in); + + // Display in-handles only when they are "initialized". + if (in.length_squared() > 0) { + info.type = HandleType::HANDLE_TYPE_IN; + const int handle_idx = idx * 3 + 0; + collected_secondary_handle_ids.append(handle_idx); + _secondary_handles_info.write[handle_idx] = info; + + secondary_handle_points.append(pos + in); + handle_lines.append(pos); + handle_lines.append(pos + in); + } } // Collect out-handles except for the last point. - if (idx < c->get_point_count() - 1) { - info.type = HandleType::HANDLE_TYPE_OUT; - const int handle_idx = idx * 3 + 1; - collected_secondary_handle_ids.append(handle_idx); - _secondary_handles_info.write[handle_idx] = info; - + if (idx < c->get_point_count() - 1 && (is_current_point_selected || is_following_point_selected)) { const Vector3 out = c->get_point_out(idx); - secondary_handle_points.append(pos + out); - handle_lines.append(pos); - handle_lines.append(pos + out); + + // Display out-handles only when they are "initialized". + if (out.length_squared() > 0) { + info.type = HandleType::HANDLE_TYPE_OUT; + const int handle_idx = idx * 3 + 1; + collected_secondary_handle_ids.append(handle_idx); + _secondary_handles_info.write[handle_idx] = info; + + secondary_handle_points.append(pos + out); + handle_lines.append(pos); + handle_lines.append(pos + out); + } } // Collect tilt-handles. - { + if (is_current_point_selected || is_previous_point_selected || is_following_point_selected) { + // Tilt handle. { info.type = HandleType::HANDLE_TYPE_TILT; const int handle_idx = idx * 3 + 2; @@ -423,7 +419,7 @@ void Path3DGizmo::redraw() { const Vector3 edge = sin(a) * side + cos(a) * up; disk.append(pos + edge * disk_size); } - add_vertices(disk, path_tilt_material, Mesh::PRIMITIVE_LINE_STRIP); + add_vertices(disk, is_current_point_selected ? path_tilt_material : path_tilt_muted_material, Mesh::PRIMITIVE_LINE_STRIP); } } } @@ -436,21 +432,27 @@ void Path3DGizmo::redraw() { add_lines(tilt_handle_lines, path_tilt_material); } - if (primary_handle_points.size()) { - add_handles(primary_handle_points, handles_material); - } if (secondary_handle_points.size()) { add_handles(secondary_handle_points, sec_handles_material, collected_secondary_handle_ids, false, true); } + // Draw the gizmo plugin manually, because handles are registered. In which case, the caller code skips drawing the gizmo plugin. + gizmo_plugin->redraw(this); } } +void Path3DGizmo::_update_transform_gizmo() { + Node3DEditor::get_singleton()->update_transform_gizmo(); +} + Path3DGizmo::Path3DGizmo(Path3D *p_path, float p_disk_size) { path = p_path; disk_size = p_disk_size; set_node_3d(p_path); orig_in_length = 0; orig_out_length = 0; + + // Connecting to a signal once, rather than plaguing the implementation with calls to `Node3DEditor::update_transform_gizmo`. + path->connect("curve_changed", callable_mp(this, &Path3DGizmo::_update_transform_gizmo)); } EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { @@ -819,10 +821,130 @@ Ref<EditorNode3DGizmo> Path3DGizmoPlugin::create_gizmo(Node3D *p_spatial) { return ref; } +bool Path3DGizmoPlugin::has_gizmo(Node3D *p_spatial) { + return Object::cast_to<Path3D>(p_spatial) != nullptr; +} + String Path3DGizmoPlugin::get_gizmo_name() const { return "Path3D"; } +void Path3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { + Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d()); + ERR_FAIL_NULL(path); + + Ref<Curve3D> curve = path->get_curve(); + + Ref<StandardMaterial3D> handle_material = get_material("handles", p_gizmo); + PackedVector3Array handles; + + for (int idx = 0; idx < curve->get_point_count(); ++idx) { + // Collect handles. + const Vector3 pos = curve->get_point_position(idx); + + handles.append(pos); + } + + if (handles.size()) { + p_gizmo->add_vertices(handles, handle_material, Mesh::PRIMITIVE_POINTS); + } +} + +int Path3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const { + Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d()); + ERR_FAIL_NULL_V(path, -1); + Ref<Curve3D> curve = path->get_curve(); + ERR_FAIL_COND_V(curve.is_null(), -1); + + for (int idx = 0; idx < curve->get_point_count(); ++idx) { + Vector3 pos = path->get_global_transform().xform(curve->get_point_position(idx)); + if (p_camera->unproject_position(pos).distance_to(p_point) < 20) { + return idx; + } + } + return -1; +} + +Vector<int> Path3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const { + Vector<int> contained_points; + + Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d()); + ERR_FAIL_NULL_V(path, contained_points); + Ref<Curve3D> curve = path->get_curve(); + ERR_FAIL_COND_V(curve.is_null(), contained_points); + + for (int idx = 0; idx < curve->get_point_count(); ++idx) { + Vector3 pos = path->get_global_transform().xform(curve->get_point_position(idx)); + bool is_contained_in_frustum = true; + for (int i = 0; i < p_frustum.size(); ++i) { + if (p_frustum[i].distance_to(pos) > 0) { + is_contained_in_frustum = false; + break; + } + } + + if (is_contained_in_frustum) { + contained_points.push_back(idx); + } + } + + return contained_points; +} + +Transform3D Path3DGizmoPlugin::get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const { + Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d()); + ERR_FAIL_NULL_V(path, Transform3D()); + Ref<Curve3D> curve = path->get_curve(); + ERR_FAIL_COND_V(curve.is_null(), Transform3D()); + ERR_FAIL_INDEX_V(p_id, curve->get_point_count(), Transform3D()); + + Basis basis = transformation_locked_basis.has(p_id) ? transformation_locked_basis[p_id] : curve->get_point_baked_posture(p_id, true); + Vector3 pos = curve->get_point_position(p_id); + + Transform3D t = Transform3D(basis, pos); + return t; +} + +void Path3DGizmoPlugin::set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) { + Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d()); + ERR_FAIL_NULL(path); + Ref<Curve3D> curve = path->get_curve(); + ERR_FAIL_COND(curve.is_null()); + ERR_FAIL_INDEX(p_id, curve->get_point_count()); + + if (!transformation_locked_basis.has(p_id)) { + transformation_locked_basis[p_id] = Basis(curve->get_point_baked_posture(p_id, true)); + } + curve->set_point_position(p_id, p_transform.origin); +} + +void Path3DGizmoPlugin::commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel) { + Path3D *path = Object::cast_to<Path3D>(p_gizmo->get_node_3d()); + ERR_FAIL_NULL(path); + Ref<Curve3D> curve = path->get_curve(); + ERR_FAIL_COND(curve.is_null()); + + transformation_locked_basis.clear(); + + if (p_cancel) { + for (int i = 0; i < p_ids.size(); ++i) { + curve->set_point_position(p_ids[i], p_restore[i].origin); + } + return; + } + + EditorUndoRedoManager *undo_redo = EditorUndoRedoManager::get_singleton(); + + undo_redo->create_action(TTR("Set Curve Point Position")); + + for (int i = 0; i < p_ids.size(); ++i) { + const int idx = p_ids[i]; + undo_redo->add_do_method(curve.ptr(), "set_point_position", idx, curve->get_point_position(idx)); + undo_redo->add_undo_method(curve.ptr(), "set_point_position", idx, p_restore[i].origin); + } + undo_redo->commit_action(); +} + int Path3DGizmoPlugin::get_priority() const { return -1; } @@ -835,6 +957,7 @@ Path3DGizmoPlugin::Path3DGizmoPlugin(float p_disk_size) { create_material("path_material", path_color); create_material("path_thin_material", Color(0.6, 0.6, 0.6)); create_material("path_tilt_material", path_tilt_color); + create_material("path_tilt_muted_material", path_tilt_color * 0.7); create_handle_material("handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorPathSmoothHandle"), EditorStringName(EditorIcons))); create_handle_material("sec_handles", false, EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("EditorCurveHandle"), EditorStringName(EditorIcons))); } diff --git a/editor/plugins/path_3d_editor_plugin.h b/editor/plugins/path_3d_editor_plugin.h index 5a5f76b015..931ac7128c 100644 --- a/editor/plugins/path_3d_editor_plugin.h +++ b/editor/plugins/path_3d_editor_plugin.h @@ -63,6 +63,8 @@ class Path3DGizmo : public EditorNode3DGizmo { // Cache information of secondary handles. Vector<HandleInfo> _secondary_handles_info; + void _update_transform_gizmo(); + public: virtual String get_handle_name(int p_id, bool p_secondary) const override; virtual Variant get_handle_value(int p_id, bool p_secondary) const override; @@ -78,11 +80,25 @@ class Path3DGizmoPlugin : public EditorNode3DGizmoPlugin { float disk_size = 0.8; + // Locking basis is meant to ensure a predictable behavior during translation of the curve points in "local space transform mode". + // Without the locking, the gizmo/point, in "local space transform mode", wouldn't follow a straight path and would curve and twitch in an unpredictable way. + HashMap<int, Basis> transformation_locked_basis; + protected: Ref<EditorNode3DGizmo> create_gizmo(Node3D *p_spatial) override; public: + virtual bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; + + virtual void redraw(EditorNode3DGizmo *p_gizmo) override; + + virtual int subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo, Camera3D *p_camera, const Vector2 &p_point) const override; + virtual Vector<int> subgizmos_intersect_frustum(const EditorNode3DGizmo *p_gizmo, const Camera3D *p_camera, const Vector<Plane> &p_frustum) const override; + virtual Transform3D get_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id) const override; + virtual void set_subgizmo_transform(const EditorNode3DGizmo *p_gizmo, int p_id, Transform3D p_transform) override; + virtual void commit_subgizmos(const EditorNode3DGizmo *p_gizmo, const Vector<int> &p_ids, const Vector<Transform3D> &p_restore, bool p_cancel = false) override; + int get_priority() const override; Path3DGizmoPlugin(float p_disk_size); }; |