UX and stability improvements for Path3D node

This commit is contained in:
matricola787 2024-03-24 13:43:17 +01:00 committed by GreenCrowDev
parent fe01776f05
commit 5c83d14698
5 changed files with 100 additions and 51 deletions

View File

@ -0,0 +1 @@
<svg enable-background="new 0 0 16 16" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><circle cx="8" cy="8" fill="none" r="5" stroke="#ff6" stroke-miterlimit="10" stroke-width="2"/><path d="m8 8v-5" fill="none" stroke="#ff6" stroke-linecap="round" stroke-miterlimit="10" stroke-width="2"/><path d="m8 1c-1.1 0-2 .9-2 2s.9 2 2 2 2-.9 2-2-.9-2-2-2z" fill="#fff"/></svg>

After

Width:  |  Height:  |  Size: 375 B

View File

@ -7789,6 +7789,10 @@ Vector<int> Node3DEditor::get_subgizmo_selection() {
return ret; return ret;
} }
void Node3DEditor::clear_subgizmo_selection(Object *p_obj) {
_clear_subgizmo_selection(p_obj);
}
void Node3DEditor::add_control_to_menu_panel(Control *p_control) { void Node3DEditor::add_control_to_menu_panel(Control *p_control) {
ERR_FAIL_NULL(p_control); ERR_FAIL_NULL(p_control);
ERR_FAIL_COND(p_control->get_parent()); ERR_FAIL_COND(p_control->get_parent());

View File

@ -893,6 +893,7 @@ public:
bool is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo); bool is_current_selected_gizmo(const EditorNode3DGizmo *p_gizmo);
bool is_subgizmo_selected(int p_id); bool is_subgizmo_selected(int p_id);
Vector<int> get_subgizmo_selection(); Vector<int> get_subgizmo_selection();
void clear_subgizmo_selection(Object *p_obj = nullptr);
Ref<EditorNode3DGizmo> get_current_hover_gizmo() const { return current_hover_gizmo; } Ref<EditorNode3DGizmo> get_current_hover_gizmo() const { return current_hover_gizmo; }
void set_current_hover_gizmo(Ref<EditorNode3DGizmo> p_gizmo) { current_hover_gizmo = p_gizmo; } void set_current_hover_gizmo(Ref<EditorNode3DGizmo> p_gizmo) { current_hover_gizmo = p_gizmo; }

View File

@ -111,7 +111,7 @@ void Path3DGizmo::set_handle(int p_id, bool p_secondary, Camera3D *p_camera, con
// Primary handles: position. // Primary handles: position.
if (!p_secondary) { if (!p_secondary) {
Vector3 inters; Vector3 inters;
// Special cas for primary handle, the handle id equals control point id. // Special case for primary handle, the handle id equals control point id.
const int idx = p_id; const int idx = p_id;
if (p.intersects_ray(ray_from, ray_dir, &inters)) { if (p.intersects_ray(ray_from, ray_dir, &inters)) {
if (Node3DEditor::get_singleton()->is_snap_enabled()) { if (Node3DEditor::get_singleton()->is_snap_enabled()) {
@ -200,6 +200,22 @@ void Path3DGizmo::commit_handle(int p_id, bool p_secondary, const Variant &p_res
EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton();
// Primary handles: position.
if (!p_secondary && !Path3DEditorPlugin::singleton->curve_edit->is_pressed()) {
// Special case 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. // Secondary handles: in, out, tilt.
const HandleInfo info = _secondary_handles_info[p_id]; const HandleInfo info = _secondary_handles_info[p_id];
const int idx = info.point_idx; const int idx = info.point_idx;
@ -263,6 +279,7 @@ void Path3DGizmo::redraw() {
Ref<StandardMaterial3D> path_thin_material = gizmo_plugin->get_material("path_thin_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> path_tilt_material = gizmo_plugin->get_material("path_tilt_material", this);
Ref<StandardMaterial3D> path_tilt_muted_material = gizmo_plugin->get_material("path_tilt_muted_material", this); Ref<StandardMaterial3D> path_tilt_muted_material = gizmo_plugin->get_material("path_tilt_muted_material", this);
Ref<StandardMaterial3D> handles_material = gizmo_plugin->get_material("handles");
Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles"); Ref<StandardMaterial3D> sec_handles_material = gizmo_plugin->get_material("sec_handles");
Ref<Curve3D> c = path->get_curve(); Ref<Curve3D> c = path->get_curve();
@ -340,56 +357,50 @@ void Path3DGizmo::redraw() {
if (Path3DEditorPlugin::singleton->get_edited_path() == path) { if (Path3DEditorPlugin::singleton->get_edited_path() == path) {
PackedVector3Array handle_lines; PackedVector3Array handle_lines;
PackedVector3Array tilt_handle_lines; PackedVector3Array tilt_handle_lines;
PackedVector3Array primary_handle_points;
PackedVector3Array secondary_handle_points; PackedVector3Array secondary_handle_points;
PackedInt32Array collected_secondary_handle_ids; // Avoid shadowing member on Node3DEditorGizmo. PackedInt32Array collected_secondary_handle_ids; // Avoid shadowing member on Node3DEditorGizmo.
_secondary_handles_info.resize(c->get_point_count() * 3); _secondary_handles_info.resize(c->get_point_count() * 3);
for (int idx = 0; idx < c->get_point_count(); idx++) { for (int idx = 0; idx < c->get_point_count(); idx++) {
// Collect primary-handles.
const Vector3 pos = c->get_point_position(idx); const Vector3 pos = c->get_point_position(idx);
bool is_current_point_selected = is_subgizmo_selected(idx); primary_handle_points.append(pos);
bool is_previous_point_selected = is_subgizmo_selected(idx - 1);
bool is_following_point_selected = is_subgizmo_selected(idx + 1);
HandleInfo info; HandleInfo info;
info.point_idx = idx; info.point_idx = idx;
// Collect in-handles except for the first point. // Collect in-handles except for the first point.
if (idx > 0 && (is_current_point_selected || is_previous_point_selected)) { if (idx > 0 && Path3DEditorPlugin::singleton->curve_edit_curve->is_pressed()) {
const Vector3 in = c->get_point_in(idx); const Vector3 in = c->get_point_in(idx);
// Display in-handles only when they are "initialized". info.type = HandleType::HANDLE_TYPE_IN;
if (in.length_squared() > 0) { const int handle_idx = idx * 3 + 0;
info.type = HandleType::HANDLE_TYPE_IN; collected_secondary_handle_ids.append(handle_idx);
const int handle_idx = idx * 3 + 0; _secondary_handles_info.write[handle_idx] = info;
collected_secondary_handle_ids.append(handle_idx);
_secondary_handles_info.write[handle_idx] = info;
secondary_handle_points.append(pos + in); secondary_handle_points.append(pos + in);
handle_lines.append(pos); handle_lines.append(pos);
handle_lines.append(pos + in); handle_lines.append(pos + in);
}
} }
// Collect out-handles except for the last point. // Collect out-handles except for the last point.
if (idx < c->get_point_count() - 1 && (is_current_point_selected || is_following_point_selected)) { if (idx < c->get_point_count() - 1 && Path3DEditorPlugin::singleton->curve_edit_curve->is_pressed()) {
const Vector3 out = c->get_point_out(idx); const Vector3 out = c->get_point_out(idx);
// Display out-handles only when they are "initialized". info.type = HandleType::HANDLE_TYPE_OUT;
if (out.length_squared() > 0) { const int handle_idx = idx * 3 + 1;
info.type = HandleType::HANDLE_TYPE_OUT; collected_secondary_handle_ids.append(handle_idx);
const int handle_idx = idx * 3 + 1; _secondary_handles_info.write[handle_idx] = info;
collected_secondary_handle_ids.append(handle_idx);
_secondary_handles_info.write[handle_idx] = info;
secondary_handle_points.append(pos + out); secondary_handle_points.append(pos + out);
handle_lines.append(pos); handle_lines.append(pos);
handle_lines.append(pos + out); handle_lines.append(pos + out);
}
} }
// Collect tilt-handles. // Collect tilt-handles.
if (is_current_point_selected || is_previous_point_selected || is_following_point_selected) { if (Path3DEditorPlugin::singleton->curve_edit_tilt->is_pressed()) {
// Tilt handle. // Tilt handle.
{ {
info.type = HandleType::HANDLE_TYPE_TILT; info.type = HandleType::HANDLE_TYPE_TILT;
@ -419,7 +430,7 @@ void Path3DGizmo::redraw() {
const Vector3 edge = sin(a) * side + cos(a) * up; const Vector3 edge = sin(a) * side + cos(a) * up;
disk.append(pos + edge * disk_size); disk.append(pos + edge * disk_size);
} }
add_vertices(disk, is_current_point_selected ? path_tilt_material : path_tilt_muted_material, Mesh::PRIMITIVE_LINE_STRIP); add_vertices(disk, path_tilt_material, Mesh::PRIMITIVE_LINE_STRIP);
} }
} }
} }
@ -432,6 +443,9 @@ void Path3DGizmo::redraw() {
add_lines(tilt_handle_lines, path_tilt_material); add_lines(tilt_handle_lines, path_tilt_material);
} }
if (!Path3DEditorPlugin::singleton->curve_edit->is_pressed() && primary_handle_points.size()) {
add_handles(primary_handle_points, handles_material);
}
if (secondary_handle_points.size()) { if (secondary_handle_points.size()) {
add_handles(secondary_handle_points, sec_handles_material, collected_secondary_handle_ids, false, true); add_handles(secondary_handle_points, sec_handles_material, collected_secondary_handle_ids, false, true);
} }
@ -453,6 +467,12 @@ Path3DGizmo::Path3DGizmo(Path3D *p_path, float p_disk_size) {
// Connecting to a signal once, rather than plaguing the implementation with calls to `Node3DEditor::update_transform_gizmo`. // 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)); path->connect("curve_changed", callable_mp(this, &Path3DGizmo::_update_transform_gizmo));
Path3DEditorPlugin::singleton->curve_edit->connect("pressed", callable_mp(this, &Path3DGizmo::redraw));
Path3DEditorPlugin::singleton->curve_edit_curve->connect("pressed", callable_mp(this, &Path3DGizmo::redraw));
Path3DEditorPlugin::singleton->curve_create->connect("pressed", callable_mp(this, &Path3DGizmo::redraw));
Path3DEditorPlugin::singleton->curve_del->connect("pressed", callable_mp(this, &Path3DGizmo::redraw));
Path3DEditorPlugin::singleton->curve_close->connect("pressed", callable_mp(this, &Path3DGizmo::redraw));
} }
EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) { EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p_camera, const Ref<InputEvent> &p_event) {
@ -612,9 +632,6 @@ EditorPlugin::AfterGUIInput Path3DEditorPlugin::forward_3d_gui_input(Camera3D *p
} }
} }
} }
if (curve_edit_curve->is_pressed()) {
mb->set_shift_pressed(true);
}
} }
return EditorPlugin::AFTER_GUI_INPUT_PASS; return EditorPlugin::AFTER_GUI_INPUT_PASS;
@ -663,8 +680,11 @@ void Path3DEditorPlugin::make_visible(bool p_visible) {
void Path3DEditorPlugin::_mode_changed(int p_mode) { void Path3DEditorPlugin::_mode_changed(int p_mode) {
curve_create->set_pressed(p_mode == MODE_CREATE); curve_create->set_pressed(p_mode == MODE_CREATE);
curve_edit_curve->set_pressed(p_mode == MODE_EDIT_CURVE); curve_edit_curve->set_pressed(p_mode == MODE_EDIT_CURVE);
curve_edit_tilt->set_pressed(p_mode == MODE_EDIT_TILT);
curve_edit->set_pressed(p_mode == MODE_EDIT); curve_edit->set_pressed(p_mode == MODE_EDIT);
curve_del->set_pressed(p_mode == MODE_DELETE); curve_del->set_pressed(p_mode == MODE_DELETE);
Node3DEditor::get_singleton()->clear_subgizmo_selection();
} }
void Path3DEditorPlugin::_close_curve() { void Path3DEditorPlugin::_close_curve() {
@ -709,6 +729,7 @@ void Path3DEditorPlugin::_update_theme() {
// See the 2D path editor for inspiration. // See the 2D path editor for inspiration.
curve_edit->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveEdit"), EditorStringName(EditorIcons))); curve_edit->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveEdit"), EditorStringName(EditorIcons)));
curve_edit_curve->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveCurve"), EditorStringName(EditorIcons))); curve_edit_curve->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveCurve"), EditorStringName(EditorIcons)));
curve_edit_tilt->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveTilt"), EditorStringName(EditorIcons)));
curve_create->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveCreate"), EditorStringName(EditorIcons))); curve_create->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveCreate"), EditorStringName(EditorIcons)));
curve_del->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveDelete"), EditorStringName(EditorIcons))); curve_del->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveDelete"), EditorStringName(EditorIcons)));
curve_close->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveClose"), EditorStringName(EditorIcons))); curve_close->set_icon(EditorNode::get_singleton()->get_editor_theme()->get_icon(SNAME("CurveClose"), EditorStringName(EditorIcons)));
@ -719,6 +740,7 @@ void Path3DEditorPlugin::_notification(int p_what) {
case NOTIFICATION_ENTER_TREE: { case NOTIFICATION_ENTER_TREE: {
curve_create->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_CREATE)); curve_create->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_CREATE));
curve_edit_curve->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_CURVE)); curve_edit_curve->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_CURVE));
curve_edit_tilt->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT_TILT));
curve_edit->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT)); curve_edit->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_EDIT));
curve_del->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE)); curve_del->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_mode_changed).bind(MODE_DELETE));
curve_close->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_close_curve)); curve_close->connect("pressed", callable_mp(this, &Path3DEditorPlugin::_close_curve));
@ -750,6 +772,7 @@ Path3DEditorPlugin::Path3DEditorPlugin() {
Ref<Path3DGizmoPlugin> gizmo_plugin = memnew(Path3DGizmoPlugin(disk_size)); Ref<Path3DGizmoPlugin> gizmo_plugin = memnew(Path3DGizmoPlugin(disk_size));
Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin); Node3DEditor::get_singleton()->add_gizmo_plugin(gizmo_plugin);
path_3d_gizmo_plugin = gizmo_plugin;
topmenu_bar = memnew(HBoxContainer); topmenu_bar = memnew(HBoxContainer);
topmenu_bar->hide(); topmenu_bar->hide();
@ -759,16 +782,23 @@ Path3DEditorPlugin::Path3DEditorPlugin() {
curve_edit->set_theme_type_variation("FlatButton"); curve_edit->set_theme_type_variation("FlatButton");
curve_edit->set_toggle_mode(true); curve_edit->set_toggle_mode(true);
curve_edit->set_focus_mode(Control::FOCUS_NONE); curve_edit->set_focus_mode(Control::FOCUS_NONE);
curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Drag: Select Control Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point")); curve_edit->set_tooltip_text(TTR("Select Points") + "\n" + TTR("Shift+Click: Select multiple Points") + "\n" + keycode_get_string((Key)KeyModifierMask::CMD_OR_CTRL) + TTR("Click: Add Point") + "\n" + TTR("Right Click: Delete Point"));
topmenu_bar->add_child(curve_edit); topmenu_bar->add_child(curve_edit);
curve_edit_curve = memnew(Button); curve_edit_curve = memnew(Button);
curve_edit_curve->set_theme_type_variation("FlatButton"); curve_edit_curve->set_theme_type_variation("FlatButton");
curve_edit_curve->set_toggle_mode(true); curve_edit_curve->set_toggle_mode(true);
curve_edit_curve->set_focus_mode(Control::FOCUS_NONE); curve_edit_curve->set_focus_mode(Control::FOCUS_NONE);
curve_edit_curve->set_tooltip_text(TTR("Select Control Points (Shift+Drag)")); curve_edit_curve->set_tooltip_text(TTR("Select Control Points") + "\n" + TTR("Shift+Click: Drag out Control Points"));
topmenu_bar->add_child(curve_edit_curve); topmenu_bar->add_child(curve_edit_curve);
curve_edit_tilt = memnew(Button);
curve_edit_tilt->set_theme_type_variation("FlatButton");
curve_edit_tilt->set_toggle_mode(true);
curve_edit_tilt->set_focus_mode(Control::FOCUS_NONE);
curve_edit_tilt->set_tooltip_text(TTR("Select Tilt Handles"));
topmenu_bar->add_child(curve_edit_tilt);
curve_create = memnew(Button); curve_create = memnew(Button);
curve_create->set_theme_type_variation("FlatButton"); curve_create->set_theme_type_variation("FlatButton");
curve_create->set_toggle_mode(true); curve_create->set_toggle_mode(true);
@ -838,11 +868,13 @@ void Path3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) {
Ref<StandardMaterial3D> handle_material = get_material("handles", p_gizmo); Ref<StandardMaterial3D> handle_material = get_material("handles", p_gizmo);
PackedVector3Array handles; PackedVector3Array handles;
for (int idx = 0; idx < curve->get_point_count(); ++idx) { if (Path3DEditorPlugin::singleton->curve_edit->is_pressed()) {
// Collect handles. for (int idx = 0; idx < curve->get_point_count(); ++idx) {
const Vector3 pos = curve->get_point_position(idx); // Collect handles.
const Vector3 pos = curve->get_point_position(idx);
handles.append(pos); handles.append(pos);
}
} }
if (handles.size()) { if (handles.size()) {
@ -856,10 +888,12 @@ int Path3DGizmoPlugin::subgizmos_intersect_ray(const EditorNode3DGizmo *p_gizmo,
Ref<Curve3D> curve = path->get_curve(); Ref<Curve3D> curve = path->get_curve();
ERR_FAIL_COND_V(curve.is_null(), -1); ERR_FAIL_COND_V(curve.is_null(), -1);
for (int idx = 0; idx < curve->get_point_count(); ++idx) { if (Path3DEditorPlugin::singleton->curve_edit->is_pressed()) {
Vector3 pos = path->get_global_transform().xform(curve->get_point_position(idx)); for (int idx = 0; idx < curve->get_point_count(); ++idx) {
if (p_camera->unproject_position(pos).distance_to(p_point) < 20) { Vector3 pos = path->get_global_transform().xform(curve->get_point_position(idx));
return idx; if (p_camera->unproject_position(pos).distance_to(p_point) < 20) {
return idx;
}
} }
} }
return -1; return -1;
@ -873,18 +907,20 @@ Vector<int> Path3DGizmoPlugin::subgizmos_intersect_frustum(const EditorNode3DGiz
Ref<Curve3D> curve = path->get_curve(); Ref<Curve3D> curve = path->get_curve();
ERR_FAIL_COND_V(curve.is_null(), contained_points); ERR_FAIL_COND_V(curve.is_null(), contained_points);
for (int idx = 0; idx < curve->get_point_count(); ++idx) { if (Path3DEditorPlugin::singleton->curve_edit->is_pressed()) {
Vector3 pos = path->get_global_transform().xform(curve->get_point_position(idx)); for (int idx = 0; idx < curve->get_point_count(); ++idx) {
bool is_contained_in_frustum = true; Vector3 pos = path->get_global_transform().xform(curve->get_point_position(idx));
for (int i = 0; i < p_frustum.size(); ++i) { bool is_contained_in_frustum = true;
if (p_frustum[i].distance_to(pos) > 0) { for (int i = 0; i < p_frustum.size(); ++i) {
is_contained_in_frustum = false; if (p_frustum[i].distance_to(pos) > 0) {
break; is_contained_in_frustum = false;
break;
}
} }
}
if (is_contained_in_frustum) { if (is_contained_in_frustum) {
contained_points.push_back(idx); contained_points.push_back(idx);
}
} }
} }

View File

@ -106,10 +106,16 @@ public:
class Path3DEditorPlugin : public EditorPlugin { class Path3DEditorPlugin : public EditorPlugin {
GDCLASS(Path3DEditorPlugin, EditorPlugin); GDCLASS(Path3DEditorPlugin, EditorPlugin);
friend class Path3DGizmo;
friend class Path3DGizmoPlugin;
Ref<Path3DGizmoPlugin> path_3d_gizmo_plugin;
HBoxContainer *topmenu_bar = nullptr; HBoxContainer *topmenu_bar = nullptr;
Button *curve_create = nullptr; Button *curve_create = nullptr;
Button *curve_edit = nullptr; Button *curve_edit = nullptr;
Button *curve_edit_curve = nullptr; Button *curve_edit_curve = nullptr;
Button *curve_edit_tilt = nullptr;
Button *curve_del = nullptr; Button *curve_del = nullptr;
Button *curve_close = nullptr; Button *curve_close = nullptr;
MenuButton *handle_menu = nullptr; MenuButton *handle_menu = nullptr;
@ -120,6 +126,7 @@ class Path3DEditorPlugin : public EditorPlugin {
MODE_CREATE, MODE_CREATE,
MODE_EDIT, MODE_EDIT,
MODE_EDIT_CURVE, MODE_EDIT_CURVE,
MODE_EDIT_TILT,
MODE_DELETE, MODE_DELETE,
ACTION_CLOSE ACTION_CLOSE
}; };