From 9fa4c62c228906a3f2a93d796882e76fe08a985d Mon Sep 17 00:00:00 2001 From: HolonProduction Date: Fri, 18 Apr 2025 14:50:53 +0200 Subject: [PATCH] Editor: Add some gizmo handles for `MeshInstance3D` --- editor/scene/3d/gizmos/gizmo_3d_helper.cpp | 119 +++++++++++++----- editor/scene/3d/gizmos/gizmo_3d_helper.h | 23 +++- .../gizmos/mesh_instance_3d_gizmo_plugin.cpp | 118 +++++++++++++++++ .../3d/gizmos/mesh_instance_3d_gizmo_plugin.h | 14 +++ 4 files changed, 240 insertions(+), 34 deletions(-) diff --git a/editor/scene/3d/gizmos/gizmo_3d_helper.cpp b/editor/scene/3d/gizmos/gizmo_3d_helper.cpp index 57f27e4568..743ab7eae0 100644 --- a/editor/scene/3d/gizmos/gizmo_3d_helper.cpp +++ b/editor/scene/3d/gizmos/gizmo_3d_helper.cpp @@ -142,26 +142,36 @@ void Gizmo3DHelper::box_commit_handle(const String &p_action_name, bool p_cancel Vector Gizmo3DHelper::cylinder_get_handles(real_t p_height, real_t p_radius) { Vector handles; - handles.push_back(Vector3(p_radius, 0, 0)); handles.push_back(Vector3(0, p_height * 0.5, 0)); handles.push_back(Vector3(0, p_height * -0.5, 0)); + handles.push_back(Vector3(p_radius, 0, 0)); return handles; } -String Gizmo3DHelper::cylinder_get_handle_name(int p_id) const { - if (p_id == 0) { - return "Radius"; - } else { +String Gizmo3DHelper::_cylinder_or_capsule_or_cone_frustum_get_handle_name(int p_id) const { + if (p_id < 2) { return "Height"; + } else { + return "Radius"; } } -void Gizmo3DHelper::_cylinder_or_capsule_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position, bool p_is_capsule) { - real_t initial_radius = initial_value.operator Vector2().x; - real_t initial_height = initial_value.operator Vector2().y; +void Gizmo3DHelper::_cylinder_or_capsule_or_cone_frustum_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius_top, real_t &r_radius_bottom, Vector3 &r_position, bool p_is_capsule, bool p_is_frustum) { + ERR_FAIL_COND(p_is_capsule && p_is_frustum); - int sign = p_id == 2 ? -1 : 1; - int axis = p_id == 0 ? 0 : 1; + real_t initial_radius_top, initial_radius_bottom, initial_height; + if (p_is_frustum) { + initial_radius_top = initial_value.operator Vector3().x; + initial_radius_bottom = initial_value.operator Vector3().y; + initial_height = initial_value.operator Vector3().z; + } else { + initial_radius_top = initial_value.operator Vector2().x; + initial_radius_bottom = initial_radius_top; + initial_height = initial_value.operator Vector2().y; + } + + int sign = p_id == 1 ? -1 : 1; + int axis = p_id >= 2 ? 0 : 1; Vector3 axis_vector; axis_vector[axis] = sign; @@ -174,20 +184,7 @@ void Gizmo3DHelper::_cylinder_or_capsule_set_handle(const Vector3 p_segment[2], d = Math::snapped(d, Node3DEditor::get_singleton()->get_translate_snap()); } - if (p_id == 0) { - // Adjust radius. - if (d < 0.001) { - d = 0.001; - } - r_radius = d; - r_cylinder_position = initial_transform.get_origin(); - - if (p_is_capsule) { - r_height = MAX(initial_height, r_radius * 2.0); - } else { - r_height = initial_height; - } - } else if (p_id == 1 || p_id == 2) { + if (p_id < 2) { // Adjust height. if (Input::get_singleton()->is_key_pressed(Key::ALT)) { r_height = d * 2.0; @@ -201,17 +198,41 @@ void Gizmo3DHelper::_cylinder_or_capsule_set_handle(const Vector3 p_segment[2], // Adjust position. if (Input::get_singleton()->is_key_pressed(Key::ALT)) { - r_cylinder_position = initial_transform.get_origin(); + r_position = initial_transform.get_origin(); } else { Vector3 offset; offset[axis] = (r_height - initial_height) * 0.5 * sign; - r_cylinder_position = initial_transform.xform(offset); + r_position = initial_transform.xform(offset); } if (p_is_capsule) { - r_radius = MIN(initial_radius, r_height / 2.0); + r_radius_top = MIN(initial_radius_top, r_height / 2.0); + r_radius_bottom = r_radius_top; } else { - r_radius = initial_radius; + r_radius_top = initial_radius_top; + r_radius_bottom = initial_radius_bottom; + } + } else { + // Adjust radius. + if (d < 0.001) { + d = 0.001; + } + if (!p_is_frustum) { + r_radius_top = d; + r_radius_bottom = d; + } else { + real_t diff = d - (initial_radius_bottom + initial_radius_top) / 2.0; + diff = MAX(MAX(-initial_radius_bottom + 0.001, -initial_radius_top + 0.001), diff); + r_radius_top = initial_radius_top + diff; + r_radius_bottom = initial_radius_bottom + diff; + } + + r_position = initial_transform.get_origin(); + + if (p_is_capsule) { + r_height = MAX(initial_height, r_radius_top * 2.0); + } else { + r_height = initial_height; } } } @@ -232,7 +253,7 @@ void Gizmo3DHelper::cylinder_commit_handle(int p_id, const String &p_radius_acti } EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); - ur->create_action(p_id == 0 ? p_radius_action_name : p_height_action_name); + ur->create_action(p_id < 2 ? p_height_action_name : p_radius_action_name); ur->add_do_property(p_radius_object, p_radius_property, p_radius_object->get(p_radius_property)); ur->add_undo_property(p_radius_object, p_radius_property, initial_value.operator Vector2().x); ur->add_do_property(p_height_object, p_height_property, p_height_object->get(p_height_property)); @@ -241,3 +262,43 @@ void Gizmo3DHelper::cylinder_commit_handle(int p_id, const String &p_radius_acti ur->add_undo_property(p_position_object, p_position_property, initial_transform.get_origin()); ur->commit_action(); } + +Vector Gizmo3DHelper::cone_frustum_get_handles(real_t p_height, real_t p_radius_top, real_t p_radius_bottom) { + Vector handles; + handles.push_back(Vector3(0, p_height * 0.5, 0)); + handles.push_back(Vector3(0, p_height * -0.5, 0)); + handles.push_back(Vector3((p_radius_top + p_radius_bottom) / 2.0, 0, 0)); + return handles; +} + +void Gizmo3DHelper::cone_frustum_commit_handle(int p_id, const String &p_radius_action_name, const String &p_height_action_name, bool p_cancel, Object *p_position_object, Object *p_height_object, Object *p_radius_top_object, Object *p_radius_bottom_object, const StringName &p_position_property, const StringName &p_height_property, const StringName &p_radius_top_property, const StringName &p_radius_bottom_property) { + if (!p_height_object) { + p_height_object = p_position_object; + } + if (!p_radius_top_object) { + p_radius_top_object = p_position_object; + } + if (!p_radius_bottom_object) { + p_radius_bottom_object = p_position_object; + } + + if (p_cancel) { + p_radius_top_object->set(p_radius_top_property, initial_value.operator Vector3().x); + p_radius_bottom_object->set(p_radius_bottom_property, initial_value.operator Vector3().y); + p_height_object->set(p_height_property, initial_value.operator Vector3().z); + p_position_object->set(p_position_property, initial_transform.get_origin()); + return; + } + + EditorUndoRedoManager *ur = EditorUndoRedoManager::get_singleton(); + ur->create_action(p_id < 2 ? p_height_action_name : p_radius_action_name); + ur->add_do_property(p_radius_top_object, p_radius_top_property, p_radius_top_object->get(p_radius_top_property)); + ur->add_do_property(p_radius_bottom_object, p_radius_bottom_property, p_radius_bottom_object->get(p_radius_bottom_property)); + ur->add_undo_property(p_radius_top_object, p_radius_top_property, initial_value.operator Vector3().x); + ur->add_undo_property(p_radius_bottom_object, p_radius_bottom_property, initial_value.operator Vector3().y); + ur->add_do_property(p_height_object, p_height_property, p_height_object->get(p_height_property)); + ur->add_undo_property(p_height_object, p_height_property, initial_value.operator Vector3().z); + ur->add_do_property(p_position_object, p_position_property, p_position_object->get(p_position_property)); + ur->add_undo_property(p_position_object, p_position_property, initial_transform.get_origin()); + ur->commit_action(); +} diff --git a/editor/scene/3d/gizmos/gizmo_3d_helper.h b/editor/scene/3d/gizmos/gizmo_3d_helper.h index 21d3747da0..49419d0bc7 100644 --- a/editor/scene/3d/gizmos/gizmo_3d_helper.h +++ b/editor/scene/3d/gizmos/gizmo_3d_helper.h @@ -42,7 +42,8 @@ class Gizmo3DHelper : public RefCounted { Transform3D initial_transform; private: - void _cylinder_or_capsule_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position, bool p_is_capsule); + void _cylinder_or_capsule_or_cone_frustum_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius_top, real_t &r_radius_bottom, Vector3 &r_position, bool p_is_capsule, bool p_is_frustum); + String _cylinder_or_capsule_or_cone_frustum_get_handle_name(int p_id) const; public: /** @@ -51,6 +52,7 @@ public: * Depending on the type of gizmo that will be used, different formats for the `p_initial_value` are required: * Box: The size of the box as `Vector3` * Cylinder or Capsule: A `Vector2` of the form `Vector2(radius, height)` + * Cone frustum: A `Vector3` of the form `Vector3(radius_top, radius_bottom, height)` */ void initialize_handle_action(const Variant &p_initial_value, const Transform3D &p_initial_transform); void get_segment(Camera3D *p_camera, const Point2 &p_point, Vector3 *r_segment); @@ -65,20 +67,31 @@ public: // Cylinder Vector cylinder_get_handles(real_t p_height, real_t p_radius); - String cylinder_get_handle_name(int p_id) const; + _FORCE_INLINE_ String cylinder_get_handle_name(int p_id) { return _cylinder_or_capsule_or_cone_frustum_get_handle_name(p_id); } _FORCE_INLINE_ void cylinder_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_cylinder_position) { - _cylinder_or_capsule_set_handle(p_segment, p_id, r_height, r_radius, r_cylinder_position, false); + real_t radius_bottom; + _cylinder_or_capsule_or_cone_frustum_set_handle(p_segment, p_id, r_height, r_radius, radius_bottom, r_cylinder_position, false, false); } void cylinder_commit_handle(int p_id, const String &p_radius_action_name, const String &p_height_action_name, bool p_cancel, Object *p_position_object, Object *p_height_object = nullptr, Object *p_radius_object = nullptr, const StringName &p_position_property = "global_position", const StringName &p_height_property = "height", const StringName &p_radius_property = "radius"); // Capsule _FORCE_INLINE_ Vector capsule_get_handles(real_t p_height, real_t p_radius) { return cylinder_get_handles(p_height, p_radius); } - _FORCE_INLINE_ String capsule_get_handle_name(int p_id) { return cylinder_get_handle_name(p_id); } + _FORCE_INLINE_ String capsule_get_handle_name(int p_id) { return _cylinder_or_capsule_or_cone_frustum_get_handle_name(p_id); } _FORCE_INLINE_ void capsule_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius, Vector3 &r_capsule_position) { - _cylinder_or_capsule_set_handle(p_segment, p_id, r_height, r_radius, r_capsule_position, true); + real_t radius_bottom; + _cylinder_or_capsule_or_cone_frustum_set_handle(p_segment, p_id, r_height, r_radius, radius_bottom, r_capsule_position, true, false); } _FORCE_INLINE_ void capsule_commit_handle(int p_id, const String &p_radius_action_name, const String &p_height_action_name, bool p_cancel, Object *p_position_object, Object *p_height_object = nullptr, Object *p_radius_object = nullptr, const StringName &p_position_property = "global_position", const StringName &p_height_property = "height", const StringName &p_radius_property = "radius") { cylinder_commit_handle(p_id, p_radius_action_name, p_height_action_name, p_cancel, p_position_object, p_height_object, p_radius_object, p_position_property, p_height_property, p_radius_property); } + + // Cone frustum + + Vector cone_frustum_get_handles(real_t p_height, real_t p_radius_top, real_t p_radius_bottom); + _FORCE_INLINE_ String cone_frustum_get_handle_name(int p_id) { return _cylinder_or_capsule_or_cone_frustum_get_handle_name(p_id); } + _FORCE_INLINE_ void cone_frustum_set_handle(const Vector3 p_segment[2], int p_id, real_t &r_height, real_t &r_radius_top, real_t &r_radius_bottom, Vector3 &r_frustum_position) { + _cylinder_or_capsule_or_cone_frustum_set_handle(p_segment, p_id, r_height, r_radius_top, r_radius_bottom, r_frustum_position, false, true); + } + void cone_frustum_commit_handle(int p_id, const String &p_radius_action_name, const String &p_height_action_name, bool p_cancel, Object *p_position_object, Object *p_height_object = nullptr, Object *p_radius_top_object = nullptr, Object *p_radius_bottom_object = nullptr, const StringName &p_position_property = "global_position", const StringName &p_height_property = "height", const StringName &p_radius_top_property = "top_radius", const StringName &p_radius_bottom_property = "bottom_radius"); }; diff --git a/editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.cpp b/editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.cpp index e65050d727..01839170d6 100644 --- a/editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.cpp +++ b/editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.cpp @@ -51,6 +51,104 @@ bool MeshInstance3DGizmoPlugin::can_be_hidden() const { return false; } +String MeshInstance3DGizmoPlugin::get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + MeshInstance3D *mesh = Object::cast_to(p_gizmo->get_node_3d()); + + if (Object::cast_to(*mesh->get_mesh())) { + return helper->capsule_get_handle_name(p_id); + } + + if (Object::cast_to(*mesh->get_mesh())) { + return helper->cone_frustum_get_handle_name(p_id); + } + + if (Object::cast_to(*mesh->get_mesh())) { + return helper->box_get_handle_name(p_id); + } + + return ""; +} + +Variant MeshInstance3DGizmoPlugin::get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const { + MeshInstance3D *mesh = Object::cast_to(p_gizmo->get_node_3d()); + + const Ref capsule_mesh = mesh->get_mesh(); + if (capsule_mesh.is_valid()) { + return Vector2(capsule_mesh->get_radius(), capsule_mesh->get_height()); + } + + const Ref cylinder_mesh = mesh->get_mesh(); + if (cylinder_mesh.is_valid()) { + return Vector3(cylinder_mesh->get_top_radius(), cylinder_mesh->get_bottom_radius(), cylinder_mesh->get_height()); + } + + const Ref box_mesh = mesh->get_mesh(); + if (box_mesh.is_valid()) { + return box_mesh->get_size(); + } + + return Variant(); +} + +void MeshInstance3DGizmoPlugin::begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) { + helper->initialize_handle_action(get_handle_value(p_gizmo, p_id, p_secondary), p_gizmo->get_node_3d()->get_global_transform()); +} + +void MeshInstance3DGizmoPlugin::set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) { + MeshInstance3D *mesh = Object::cast_to(p_gizmo->get_node_3d()); + + Vector3 segment[2]; + helper->get_segment(p_camera, p_point, segment); + + const Ref capsule_mesh = mesh->get_mesh(); + if (capsule_mesh.is_valid()) { + real_t height, radius; + Vector3 position; + helper->capsule_set_handle(segment, p_id, height, radius, position); + capsule_mesh->set_height(height); + capsule_mesh->set_radius(radius); + mesh->set_global_position(position); + } + + const Ref cylinder_mesh = mesh->get_mesh(); + if (cylinder_mesh.is_valid()) { + real_t height, radius_top, radius_bottom; + Vector3 position; + helper->cone_frustum_set_handle(segment, p_id, height, radius_top, radius_bottom, position); + cylinder_mesh->set_height(height); + cylinder_mesh->set_top_radius(radius_top); + cylinder_mesh->set_bottom_radius(radius_bottom); + mesh->set_global_position(position); + } + + const Ref box_mesh = mesh->get_mesh(); + if (box_mesh.is_valid()) { + Vector3 box_size, position; + helper->box_set_handle(segment, p_id, box_size, position); + box_mesh->set_size(box_size); + mesh->set_global_position(position); + } +} + +void MeshInstance3DGizmoPlugin::commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel) { + MeshInstance3D *mesh = Object::cast_to(p_gizmo->get_node_3d()); + + const Ref capsule_mesh = mesh->get_mesh(); + if (capsule_mesh.is_valid()) { + helper->cylinder_commit_handle(p_id, TTR("Change Capsule Mesh Radius"), TTR("Change Capsule Mesh Height"), p_cancel, mesh, *capsule_mesh, *capsule_mesh); + } + + const Ref cylinder_mesh = mesh->get_mesh(); + if (cylinder_mesh.is_valid()) { + helper->cone_frustum_commit_handle(p_id, TTR("Change Cylinder Mesh Radius"), TTR("Change Cylinder Mesh Height"), p_cancel, mesh, *cylinder_mesh, *cylinder_mesh, *cylinder_mesh); + } + + const Ref box_mesh = mesh->get_mesh(); + if (box_mesh.is_valid()) { + helper->box_commit_handle(TTR("Change Box Mesh Size"), p_cancel, mesh, *box_mesh); + } +} + void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { MeshInstance3D *mesh = Object::cast_to(p_gizmo->get_node_3d()); @@ -58,6 +156,8 @@ void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { Ref m = mesh->get_mesh(); + const Ref handles_material = get_material("handles"); + if (m.is_null()) { return; //none } @@ -81,4 +181,22 @@ void MeshInstance3DGizmoPlugin::redraw(EditorNode3DGizmo *p_gizmo) { if (tm.is_valid()) { p_gizmo->add_collision_triangles(tm); } + + const Ref capsule_mesh = mesh->get_mesh(); + if (capsule_mesh.is_valid()) { + const Vector handles = helper->capsule_get_handles(capsule_mesh->get_height(), capsule_mesh->get_radius()); + p_gizmo->add_handles(handles, handles_material); + } + + const Ref cylinder_mesh = mesh->get_mesh(); + if (cylinder_mesh.is_valid()) { + const Vector handles = helper->cone_frustum_get_handles(cylinder_mesh->get_height(), cylinder_mesh->get_top_radius(), cylinder_mesh->get_bottom_radius()); + p_gizmo->add_handles(handles, handles_material); + } + + const Ref box_mesh = mesh->get_mesh(); + if (box_mesh.is_valid()) { + const Vector handles = helper->box_get_handles(box_mesh->get_size()); + p_gizmo->add_handles(handles, handles_material); + } } diff --git a/editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h b/editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h index 7589aa9561..f4763f7912 100644 --- a/editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h +++ b/editor/scene/3d/gizmos/mesh_instance_3d_gizmo_plugin.h @@ -30,15 +30,29 @@ #pragma once +#include "editor/scene/3d/gizmos/gizmo_3d_helper.h" #include "editor/scene/3d/node_3d_editor_gizmos.h" class MeshInstance3DGizmoPlugin : public EditorNode3DGizmoPlugin { GDCLASS(MeshInstance3DGizmoPlugin, EditorNode3DGizmoPlugin); + Ref helper; + public: bool has_gizmo(Node3D *p_spatial) override; String get_gizmo_name() const override; int get_priority() const override; bool can_be_hidden() const override; void redraw(EditorNode3DGizmo *p_gizmo) override; + + String get_handle_name(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + Variant get_handle_value(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) const override; + void begin_handle_action(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary) override; + void set_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, Camera3D *p_camera, const Point2 &p_point) override; + void commit_handle(const EditorNode3DGizmo *p_gizmo, int p_id, bool p_secondary, const Variant &p_restore, bool p_cancel = false) override; + + MeshInstance3DGizmoPlugin() { + helper.instantiate(); + create_handle_material("handles"); + } };